From 2021ee1293ff735982a2b0a59ec45af82257e7b1 Mon Sep 17 00:00:00 2001 From: GRayHook Date: Wed, 17 Jun 2026 12:30:17 +0700 Subject: [PATCH] =?UTF-8?q?Phase=209:=20track=20skill=20blocks=20on=20mess?= =?UTF-8?q?age=5Fend=20for=20user=20messages=20=E2=80=94=20SPEC=20=C2=A76.?= =?UTF-8?q?2=20#2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scan finalized user message text for expanded skill XML blocks and upsert tracked skills using registered metadata when available. Co-authored-by: Cursor --- src/detect.ts | 21 +++++++++++++++++++++ src/index.ts | 21 ++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/detect.ts b/src/detect.ts index 5f377b7..9757c0e 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -21,6 +21,27 @@ function normalizePathForCompare(filePath: string): string { return normalize(filePath); } +/** Text from user message content (string or text blocks). */ +export function userMessageText(content: unknown): string { + if (typeof content === "string") { + return content; + } + if (!Array.isArray(content)) { + return ""; + } + const parts: string[] = []; + for (const part of content) { + if (!part || typeof part !== "object") { + continue; + } + const block = part as { type?: string; text?: string }; + if (block.type === "text" && typeof block.text === "string") { + parts.push(block.text); + } + } + return parts.join("\n"); +} + /** Returns skill name when text is a slash skill command, else null. */ export function detectSlashSkill(text: string): string | null { const match = SLASH_SKILL_RE.exec(text); diff --git a/src/index.ts b/src/index.ts index a44c35e..3ed8865 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ +import { dirname } from "node:path"; import type { ExtensionAPI, Skill } from "@earendil-works/pi-coding-agent"; -import { detectSlashSkill } from "./detect.js"; +import { detectSlashSkill, parseSkillBlocksFromText, userMessageText } from "./detect.js"; import { findRegisteredSkillByName, resolveRegisteredSkills } from "./skills-registry.js"; import { createInitialState, trackSkill } from "./state.js"; @@ -32,4 +33,22 @@ export default function skillReinject(pi: ExtensionAPI): void { 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", + }); + } + }); }