Files
pi-auto-reinject/src/index.ts
T
grayhook edc01d1079 Phase 9: track read tool paths to SKILL.md on tool_call — SPEC §6.2 #3.
Match read tool paths against registered skills when trackReadPaths is enabled and upsert with source read.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 12:30:57 +07:00

83 lines
2.5 KiB
TypeScript

import { dirname } from "node:path";
import { isToolCallEventType, type ExtensionAPI, type ExtensionContext, type Skill } from "@earendil-works/pi-coding-agent";
import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js";
import { readSettings } from "./settings.js";
import { findRegisteredSkillByName, resolveRegisteredSkills } from "./skills-registry.js";
import { createInitialState, trackSkill, type ExtensionState } from "./state.js";
function trackReadSkillPath(
path: string,
ctx: ExtensionContext,
state: ExtensionState,
registeredSkills: Skill[],
): void {
const settings = readSettings(ctx);
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
const matched = matchReadPathToSkillWhenEnabled(path, skills, settings.trackReadPaths);
if (!matched) {
return;
}
trackSkill(state, {
name: matched.name,
filePath: matched.filePath,
baseDir: matched.baseDir,
source: "read",
});
}
export default function skillReinject(pi: ExtensionAPI): void {
const state = createInitialState();
let registeredSkills: Skill[] = [];
pi.on("before_agent_start", async (event) => {
registeredSkills = event.systemPromptOptions.skills ?? registeredSkills;
});
pi.on("input", async (event, ctx) => {
if (event.source === "extension") {
return { action: "continue" };
}
const skillName = detectSlashSkill(event.text);
if (skillName) {
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
const skill = findRegisteredSkillByName(skills, skillName);
if (skill) {
trackSkill(state, {
name: skill.name,
filePath: skill.filePath,
baseDir: skill.baseDir,
source: "slash",
});
}
}
return { action: "continue" };
});
pi.on("message_end", async (event, ctx) => {
if (event.message.role !== "user") {
return;
}
const text = userMessageText(event.message.content);
const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills);
for (const block of parseSkillBlocksFromText(text)) {
const registered = findRegisteredSkillByName(skills, block.name);
trackSkill(state, {
name: block.name,
filePath: registered?.filePath ?? block.location,
baseDir: registered?.baseDir ?? dirname(block.location),
source: "skill-block",
});
}
});
pi.on("tool_call", async (event, ctx) => {
if (!isToolCallEventType("read", event)) {
return;
}
trackReadSkillPath(event.input.path, ctx, state, registeredSkills);
});
}