pi-hermes-memory 0.7.9 → 0.7.11
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 +31 -7
- package/docs/images/source-architecture.svg +1 -1
- package/docs/mermaid/source-architecture.mmd +0 -4
- package/package.json +1 -1
- package/src/config.ts +9 -2
- package/src/constants.ts +42 -6
- package/src/handlers/learn-memory.ts +1 -1
- package/src/handlers/skills-command.ts +116 -15
- package/src/handlers/sync-markdown-memories.ts +10 -4
- package/src/index.ts +10 -14
- package/src/paths.ts +57 -0
- package/src/project-memory-migration.ts +1 -2
- package/src/project.ts +2 -1
- package/src/store/skill-store.ts +7 -3
- package/src/store/skill-utils.ts +23 -6
- package/src/tools/skill-tool.ts +166 -31
- package/src/types.ts +5 -5
- package/src/handlers/skill-auto-trigger.ts +0 -128
- package/src/skills/procedural-skill-creator/SKILL.md +0 -146
package/src/tools/skill-tool.ts
CHANGED
|
@@ -9,6 +9,82 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
9
9
|
import { SkillStore } from "../store/skill-store.js";
|
|
10
10
|
import { SKILL_TOOL_DESCRIPTION } from "../constants.js";
|
|
11
11
|
|
|
12
|
+
function normalizeTextList(value: unknown): string[] {
|
|
13
|
+
if (!Array.isArray(value)) return [];
|
|
14
|
+
return value
|
|
15
|
+
.filter((item): item is string => typeof item === "string")
|
|
16
|
+
.map((item) => item.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatOrderedList(items: string[]): string {
|
|
21
|
+
return items.map((item, index) => `${index + 1}. ${item}`).join("\n");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function formatBulletList(items: string[], fallback: string): string {
|
|
25
|
+
if (items.length === 0) return `- ${fallback}`;
|
|
26
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildStructuredSkillBody(
|
|
30
|
+
whenToUse: string,
|
|
31
|
+
procedureSteps: string[],
|
|
32
|
+
pitfalls: string[],
|
|
33
|
+
verificationSteps: string[],
|
|
34
|
+
): string {
|
|
35
|
+
return [
|
|
36
|
+
"## When to Use",
|
|
37
|
+
whenToUse,
|
|
38
|
+
"",
|
|
39
|
+
"## Procedure",
|
|
40
|
+
formatOrderedList(procedureSteps),
|
|
41
|
+
"",
|
|
42
|
+
"## Pitfalls",
|
|
43
|
+
formatBulletList(pitfalls, "No notable pitfalls recorded yet."),
|
|
44
|
+
"",
|
|
45
|
+
"## Verification",
|
|
46
|
+
formatOrderedList(verificationSteps),
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const SKILL_ID_PARAM = Type.String({
|
|
51
|
+
description: "Stable skill id for view/patch/update/delete. e.g., 'global:debug-typescript-errors' or 'project:my-repo:release-app'. Legacy alias 'edit' also accepts this field.",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const SKILL_TOOL_PARAMETERS = Type.Object({
|
|
55
|
+
action: StringEnum(["create", "view", "patch", "update", "edit", "delete"] as const, {
|
|
56
|
+
description: "The skill action to perform.",
|
|
57
|
+
}),
|
|
58
|
+
name: Type.Optional(Type.String({
|
|
59
|
+
description: "Skill name for create. e.g., 'debug-typescript-errors'.",
|
|
60
|
+
})),
|
|
61
|
+
skill_id: Type.Optional(SKILL_ID_PARAM),
|
|
62
|
+
description: Type.Optional(Type.String({
|
|
63
|
+
description: "One-line description of when to use this skill. Required for create; optional for update/edit.",
|
|
64
|
+
})),
|
|
65
|
+
scope: Type.Optional(StringEnum(["global", "project"] as const, {
|
|
66
|
+
description: "Required for create. Use 'global' for portable procedures and 'project' for repo-specific workflows.",
|
|
67
|
+
})),
|
|
68
|
+
section: Type.Optional(Type.String({
|
|
69
|
+
description: "Required for patch. Section header to patch. e.g., 'Procedure', 'Pitfalls'.",
|
|
70
|
+
})),
|
|
71
|
+
content: Type.Optional(Type.String({
|
|
72
|
+
description: "Raw markdown body for create/update/edit, or new section content for patch. For create/update/edit you can provide this or the structured fields below.",
|
|
73
|
+
})),
|
|
74
|
+
when_to_use: Type.Optional(Type.String({
|
|
75
|
+
description: "Structured create/update/edit field. Explain when this skill should be used and where its boundaries are.",
|
|
76
|
+
})),
|
|
77
|
+
procedure_steps: Type.Optional(Type.Array(Type.String(), {
|
|
78
|
+
description: "Structured create/update/edit field. Ordered concrete steps for the workflow.",
|
|
79
|
+
})),
|
|
80
|
+
pitfalls: Type.Optional(Type.Array(Type.String(), {
|
|
81
|
+
description: "Structured create/update/edit field. Optional common mistakes, caveats, or failure modes to avoid.",
|
|
82
|
+
})),
|
|
83
|
+
verification_steps: Type.Optional(Type.Array(Type.String(), {
|
|
84
|
+
description: "Structured create/update/edit field. Concrete checks that confirm the workflow succeeded.",
|
|
85
|
+
})),
|
|
86
|
+
}, { additionalProperties: false });
|
|
87
|
+
|
|
12
88
|
export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
|
|
13
89
|
pi.registerTool({
|
|
14
90
|
name: "skill",
|
|
@@ -17,33 +93,67 @@ export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
|
|
|
17
93
|
promptSnippet: "Save or manage reusable procedures and patterns",
|
|
18
94
|
promptGuidelines: [
|
|
19
95
|
"Use the skill tool after completing complex tasks that required trial and error or multiple tool calls.",
|
|
20
|
-
"Use 'create' to save a new reusable procedure, 'patch' to update a section of an existing skill by skill_id.",
|
|
21
|
-
"
|
|
96
|
+
"Use 'create' to save a new reusable procedure, 'patch' to update a section of an existing skill by skill_id, and 'update' for a full rewrite.",
|
|
97
|
+
"Scope is required on create: choose scope='global' for transferable procedures and scope='project' when the workflow depends on this repo's paths, scripts, conventions, or deploy steps.",
|
|
98
|
+
"Prefer structured fields for create/update: when_to_use, procedure_steps, pitfalls, and verification_steps. The tool will render valid SKILL.md sections for you.",
|
|
99
|
+
"Use 'view' before patching or updating when you need to inspect an existing skill.",
|
|
22
100
|
"Do NOT use skills for temporary task state — only for durable, reusable procedures.",
|
|
23
101
|
],
|
|
24
|
-
parameters:
|
|
25
|
-
action: StringEnum(["create", "view", "patch", "edit", "delete"] as const),
|
|
26
|
-
name: Type.Optional(
|
|
27
|
-
Type.String({ description: "Skill name (for create). e.g., 'debug-typescript-errors'" })
|
|
28
|
-
),
|
|
29
|
-
skill_id: Type.Optional(
|
|
30
|
-
Type.String({ description: "Stable skill id for view/patch/edit/delete. e.g., 'global:debug-typescript-errors' or 'project:my-repo:release-app'" })
|
|
31
|
-
),
|
|
32
|
-
description: Type.Optional(
|
|
33
|
-
Type.String({ description: "One-line description of when to use this skill (for create/edit)" })
|
|
34
|
-
),
|
|
35
|
-
scope: Type.Optional(
|
|
36
|
-
StringEnum(["global", "project"] as const, { description: "Optional creation scope. Omit to let the extension classify it automatically." })
|
|
37
|
-
),
|
|
38
|
-
section: Type.Optional(
|
|
39
|
-
Type.String({ description: "Section header to patch (for patch action). e.g., 'Procedure', 'Pitfalls'" })
|
|
40
|
-
),
|
|
41
|
-
content: Type.Optional(
|
|
42
|
-
Type.String({ description: "Body content for create, new section content for patch, or new body for edit" })
|
|
43
|
-
),
|
|
44
|
-
}),
|
|
102
|
+
parameters: SKILL_TOOL_PARAMETERS,
|
|
45
103
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
46
|
-
const
|
|
104
|
+
const skillParams = params as {
|
|
105
|
+
action: "create" | "view" | "patch" | "update" | "edit" | "delete";
|
|
106
|
+
name?: string;
|
|
107
|
+
skill_id?: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
scope?: "global" | "project";
|
|
110
|
+
section?: string;
|
|
111
|
+
content?: string;
|
|
112
|
+
when_to_use?: string;
|
|
113
|
+
procedure_steps?: unknown;
|
|
114
|
+
pitfalls?: unknown;
|
|
115
|
+
verification_steps?: unknown;
|
|
116
|
+
};
|
|
117
|
+
const {
|
|
118
|
+
action,
|
|
119
|
+
name,
|
|
120
|
+
skill_id,
|
|
121
|
+
description,
|
|
122
|
+
scope,
|
|
123
|
+
section,
|
|
124
|
+
content,
|
|
125
|
+
when_to_use,
|
|
126
|
+
procedure_steps,
|
|
127
|
+
pitfalls,
|
|
128
|
+
verification_steps,
|
|
129
|
+
} = skillParams;
|
|
130
|
+
|
|
131
|
+
const whenToUse = typeof when_to_use === "string" ? when_to_use.trim() : "";
|
|
132
|
+
const procedureSteps = normalizeTextList(procedure_steps);
|
|
133
|
+
const pitfallItems = normalizeTextList(pitfalls);
|
|
134
|
+
const verificationSteps = normalizeTextList(verification_steps);
|
|
135
|
+
const hasStructuredBody = Boolean(whenToUse) || procedureSteps.length > 0 || pitfallItems.length > 0 || verificationSteps.length > 0;
|
|
136
|
+
|
|
137
|
+
const buildBodyOrError = () => {
|
|
138
|
+
if (content?.trim()) return { body: content.trim() };
|
|
139
|
+
if (!hasStructuredBody) {
|
|
140
|
+
return {
|
|
141
|
+
error: "Either content or structured fields are required. Prefer when_to_use, procedure_steps, pitfalls, and verification_steps for create/update.",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (!whenToUse) {
|
|
145
|
+
return { error: "when_to_use is required when content is omitted." };
|
|
146
|
+
}
|
|
147
|
+
if (procedureSteps.length === 0) {
|
|
148
|
+
return { error: "procedure_steps is required when content is omitted." };
|
|
149
|
+
}
|
|
150
|
+
if (verificationSteps.length === 0) {
|
|
151
|
+
return { error: "verification_steps is required when content is omitted." };
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
body: buildStructuredSkillBody(whenToUse, procedureSteps, pitfallItems, verificationSteps),
|
|
155
|
+
};
|
|
156
|
+
};
|
|
47
157
|
|
|
48
158
|
let result;
|
|
49
159
|
switch (action) {
|
|
@@ -60,13 +170,20 @@ export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
|
|
|
60
170
|
details: {},
|
|
61
171
|
};
|
|
62
172
|
}
|
|
63
|
-
|
|
173
|
+
const createBodyResult = buildBodyOrError();
|
|
174
|
+
if (!createBodyResult.body) {
|
|
64
175
|
return {
|
|
65
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error:
|
|
176
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: createBodyResult.error }) }],
|
|
66
177
|
details: {},
|
|
67
178
|
};
|
|
68
179
|
}
|
|
69
|
-
|
|
180
|
+
if (!scope) {
|
|
181
|
+
return {
|
|
182
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "scope is required for 'create' action. Use 'global' or 'project'." }) }],
|
|
183
|
+
details: {},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
result = await store.create(name, description, createBodyResult.body, scope);
|
|
70
187
|
break;
|
|
71
188
|
|
|
72
189
|
case "view":
|
|
@@ -109,15 +226,33 @@ export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
|
|
|
109
226
|
result = await store.patch(skill_id, section, content);
|
|
110
227
|
break;
|
|
111
228
|
|
|
112
|
-
case "
|
|
229
|
+
case "update":
|
|
230
|
+
case "edit": {
|
|
231
|
+
const updateActionLabel = action === "edit" ? "edit" : "update";
|
|
113
232
|
if (!skill_id) {
|
|
114
233
|
return {
|
|
115
|
-
content: [{ type: "text", text: JSON.stringify({ success: false, error:
|
|
234
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: `skill_id is required for '${updateActionLabel}' action.` }) }],
|
|
235
|
+
details: {},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const updateBodyResult = buildBodyOrError();
|
|
239
|
+
const nextDescription = description?.trim() || "";
|
|
240
|
+
const nextBody = updateBodyResult.body ?? content?.trim() ?? "";
|
|
241
|
+
if (!nextDescription && !nextBody) {
|
|
242
|
+
return {
|
|
243
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: `Provide description, content, or structured fields for '${updateActionLabel}'.` }) }],
|
|
244
|
+
details: {},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (hasStructuredBody && !updateBodyResult.body) {
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: updateBodyResult.error }) }],
|
|
116
250
|
details: {},
|
|
117
251
|
};
|
|
118
252
|
}
|
|
119
|
-
result = await store.edit(skill_id,
|
|
253
|
+
result = await store.edit(skill_id, nextDescription, nextBody);
|
|
120
254
|
break;
|
|
255
|
+
}
|
|
121
256
|
|
|
122
257
|
case "delete":
|
|
123
258
|
if (!skill_id) {
|
|
@@ -132,7 +267,7 @@ export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
|
|
|
132
267
|
default:
|
|
133
268
|
result = {
|
|
134
269
|
success: false,
|
|
135
|
-
error: `Unknown action '${action}'. Use: create, view, patch,
|
|
270
|
+
error: `Unknown action '${action}'. Use: create, view, patch, update, delete`,
|
|
136
271
|
};
|
|
137
272
|
}
|
|
138
273
|
|
package/src/types.ts
CHANGED
|
@@ -126,6 +126,10 @@ export interface SkillIndex {
|
|
|
126
126
|
displayName?: string;
|
|
127
127
|
/** Short description shown in skill listings */
|
|
128
128
|
description: string;
|
|
129
|
+
/** ISO date created */
|
|
130
|
+
created: string;
|
|
131
|
+
/** ISO date last updated */
|
|
132
|
+
updated: string;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
export interface SkillDocument extends SkillIndex {
|
|
@@ -133,10 +137,6 @@ export interface SkillDocument extends SkillIndex {
|
|
|
133
137
|
body: string;
|
|
134
138
|
/** Version number */
|
|
135
139
|
version: number;
|
|
136
|
-
/** ISO date created */
|
|
137
|
-
created: string;
|
|
138
|
-
/** ISO date last updated */
|
|
139
|
-
updated: string;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
export interface SkillResult {
|
|
@@ -149,7 +149,7 @@ export interface SkillResult {
|
|
|
149
149
|
path?: string;
|
|
150
150
|
conflictType?: "duplicate" | "similar" | "name-collision" | "scope-conflict";
|
|
151
151
|
similarSkillIds?: string[];
|
|
152
|
-
suggestedAction?: "patch" | "
|
|
152
|
+
suggestedAction?: "patch" | "update" | "rename";
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skill auto-trigger — after complex tasks (8+ tool calls, 2+ distinct tool types),
|
|
3
|
-
* trigger automatic skill extraction via pi.exec().
|
|
4
|
-
*
|
|
5
|
-
* This implements Hermes' "self-evaluation checkpoint" pattern.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import { MemoryStore } from "../store/memory-store.js";
|
|
12
|
-
import { SkillStore } from "../store/skill-store.js";
|
|
13
|
-
import { COMBINED_REVIEW_PROMPT, DEFAULT_SKILL_TRIGGER_TOOL_CALLS, ENTRY_DELIMITER } from "../constants.js";
|
|
14
|
-
import type { MemoryConfig } from "../types.js";
|
|
15
|
-
import { getMessageText } from "../types.js";
|
|
16
|
-
|
|
17
|
-
const proceduralSkillCreatorPath = path.join(
|
|
18
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
19
|
-
"..",
|
|
20
|
-
"skills",
|
|
21
|
-
"procedural-skill-creator",
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
export function setupSkillAutoTrigger(
|
|
25
|
-
pi: ExtensionAPI,
|
|
26
|
-
store: MemoryStore,
|
|
27
|
-
skillStore: SkillStore,
|
|
28
|
-
config: MemoryConfig,
|
|
29
|
-
): void {
|
|
30
|
-
let triggeredThisSession = false;
|
|
31
|
-
|
|
32
|
-
// Accumulate tool calls across turns (reset on trigger)
|
|
33
|
-
let toolCallCount = 0;
|
|
34
|
-
const toolTypes = new Set<string>();
|
|
35
|
-
|
|
36
|
-
pi.on("turn_end", async (event, ctx) => {
|
|
37
|
-
if (triggeredThisSession) return;
|
|
38
|
-
|
|
39
|
-
// Count tool calls from this turn's message only (not cumulative branch scan —
|
|
40
|
-
// otherwise the counter accumulates historical tool calls and fires prematurely).
|
|
41
|
-
try {
|
|
42
|
-
const msg = event.message;
|
|
43
|
-
if (msg?.role === "assistant") {
|
|
44
|
-
const content = msg?.content;
|
|
45
|
-
if (Array.isArray(content)) {
|
|
46
|
-
for (const block of content) {
|
|
47
|
-
if (block && typeof block === "object" && block.type === "toolCall") {
|
|
48
|
-
toolCallCount++;
|
|
49
|
-
if ((block as { name?: string }).name) toolTypes.add((block as { name: string }).name);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} catch {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Require 8+ tool calls AND 2+ distinct tool types
|
|
59
|
-
if (toolCallCount < DEFAULT_SKILL_TRIGGER_TOOL_CALLS) return;
|
|
60
|
-
if (toolTypes.size < 2) return;
|
|
61
|
-
|
|
62
|
-
triggeredThisSession = true;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
// Build conversation context
|
|
66
|
-
const branch = ctx.sessionManager.getBranch();
|
|
67
|
-
const parts: string[] = [];
|
|
68
|
-
|
|
69
|
-
for (const entry of branch) {
|
|
70
|
-
if (entry.type !== "message") continue;
|
|
71
|
-
const msg = entry.message;
|
|
72
|
-
const text = getMessageText(msg);
|
|
73
|
-
if (!text) continue;
|
|
74
|
-
const prefix = msg.role === "user" ? "[USER]" : "[ASSISTANT]";
|
|
75
|
-
parts.push(`${prefix}: ${text}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Only include recent context
|
|
79
|
-
const recentParts = parts.slice(-10);
|
|
80
|
-
|
|
81
|
-
const currentMemory = store.getMemoryEntries().join(ENTRY_DELIMITER);
|
|
82
|
-
const skillIndex = await skillStore.loadIndex();
|
|
83
|
-
const skillSummary = skillIndex
|
|
84
|
-
.map((skill) => `${skill.skillId} [${skill.scope}]: ${skill.displayName || skill.name} - ${skill.description}`)
|
|
85
|
-
.join("\n");
|
|
86
|
-
|
|
87
|
-
const activeProjectName = skillStore.getProjectName();
|
|
88
|
-
const scopeLine = activeProjectName
|
|
89
|
-
? `Active project context: '${activeProjectName}'. If workflow details depend on this project, use scope='project'; otherwise use scope='global'.`
|
|
90
|
-
: "No active project context detected. Use scope='global' unless the workflow is clearly project-specific.";
|
|
91
|
-
|
|
92
|
-
const prompt = [
|
|
93
|
-
"This was a complex task that required multiple tool calls. Extract any reusable procedures as skills.",
|
|
94
|
-
"A bundled skill named procedural-skill-creator is loaded for you. Read and follow it before deciding whether to create or patch a skill.",
|
|
95
|
-
"Always pass scope explicitly when creating a skill: scope='global' or scope='project'.",
|
|
96
|
-
"Choose scope='global' for transferable procedures and scope='project' when the workflow depends on this repo's paths, scripts, architecture, deploy steps, or conventions.",
|
|
97
|
-
scopeLine,
|
|
98
|
-
"",
|
|
99
|
-
"--- Existing Skills ---",
|
|
100
|
-
skillSummary || "(none)",
|
|
101
|
-
"",
|
|
102
|
-
"--- Current Memory ---",
|
|
103
|
-
currentMemory || "(empty)",
|
|
104
|
-
"",
|
|
105
|
-
"--- Recent Conversation ---",
|
|
106
|
-
recentParts.join("\n\n"),
|
|
107
|
-
"",
|
|
108
|
-
"If a skill should be created, use the skill tool with action 'create'.",
|
|
109
|
-
"If a related skill already exists, use 'patch' with its skill_id to update it.",
|
|
110
|
-
"If nothing reusable happened, say 'Nothing to extract.' and stop.",
|
|
111
|
-
].join("\n");
|
|
112
|
-
|
|
113
|
-
const result = await pi.exec("pi", ["-p", "--no-session", "--skill", proceduralSkillCreatorPath, prompt], {
|
|
114
|
-
signal: ctx.signal,
|
|
115
|
-
timeout: 60000,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
if (result.code === 0 && result.stdout) {
|
|
119
|
-
const output = result.stdout.trim();
|
|
120
|
-
if (output && !output.toLowerCase().includes("nothing to extract")) {
|
|
121
|
-
ctx.ui.notify("🧠 Complex task detected — skill extracted", "info");
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
} catch {
|
|
125
|
-
// Best-effort — don't block
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
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.
|