feat: guida AGGIUNGERE-SKILL.md, tool skill_sync e linee guida creazione/sync skill nell'iniezione
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Aggiungere una nuova skill al Skill Hub
|
||||
|
||||
Guida operativa per l'agente (e per l'utente). Le skill vivono nel package
|
||||
`pi-skill-hub` (canonico: git git.enne2.net/enne2/pi-skill-hub).
|
||||
|
||||
## 1. Creare la skill
|
||||
1. Crea la directory `skills/<nome-kebab-case>/` dentro il package con:
|
||||
- `SKILL.md` — frontmatter (name uguale al nome dir, description con
|
||||
trigger "usa quando…" ed esclusioni "non usare per…") + corpo operativo
|
||||
(Outcome, Preconditions, Non-negotiable gates, Workflow, Salvataggio,
|
||||
Verifica/exit criteria, Resource loading)
|
||||
- `references/*.md` — dettagli tecnici, catalogo errori, dati di configurazione
|
||||
- `scripts/…` — helper deterministici (niente segreti, niente path
|
||||
macchina-specifici: quelli vanno in qmem nei project `host-*`)
|
||||
2. Rispetta i limiti della spec Agent Skills: SKILL.md ≤ ~500 righe,
|
||||
dettagli in references, descrizione ≤ 1024 caratteri.
|
||||
3. I contenuti che oggi vivono in qmem NON si duplicano: in qmem restano solo
|
||||
puntatori (project della skill) ed evidenze di esecuzione.
|
||||
|
||||
## 2. Validare in locale
|
||||
- Testa la skill sul task reale (o harness); registra l'esito in qmem
|
||||
(project della skill o `skills/<nome>/validazioni`).
|
||||
- `pi -e <package>` per provarla senza installare.
|
||||
|
||||
## 3. Sincronizzare sul Git remoto
|
||||
- Usa il tool `skill_sync` (message opzionale): esegue
|
||||
`pull --ff-only` → `git add skills/` → commit → push su `origin main`.
|
||||
Su errore (conflitto/rete) riporta il messaggio: NON fare rebase/merge
|
||||
automatici; chiedere all'utente.
|
||||
- Le altre macchine ricevono con l'auto-pull all'avvio di sessione, oppure
|
||||
`pi update --extensions` (o `git -C <clone> pull` + `/skill-sync`).
|
||||
|
||||
## 4. Versionamento
|
||||
- Commit piccoli e descrittivi; tag `v<maggiore>` per cambiamenti di contratto
|
||||
(macchine che vogliono stabilità installano `@<tag>`).
|
||||
- Modifiche che cambiano regole operative (es. nuove regole vincolanti) vanno
|
||||
segnalate all'utente prima del push (review umana = promotion gate).
|
||||
+68
-6
@@ -15,7 +15,7 @@ import { Type } from "typebox";
|
||||
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, execFile } from "node:child_process";
|
||||
|
||||
const SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "skills");
|
||||
const MAX_CHARS_PER_SKILL = 200_000;
|
||||
@@ -241,6 +241,40 @@ function searchSkills(query: string, limit: number): { entry: SkillEntry; score:
|
||||
|
||||
// ---------------------------------------------------------------- extension
|
||||
|
||||
function gitExec(root: string, args: string[], timeoutMs = 30_000): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("git", ["-C", root, ...args], { timeout: timeoutMs, encoding: "utf8" }, (err, stdout, stderr) => {
|
||||
if (err) reject(new Error(`git ${args.join(" ")} → ${String(stderr || "").trim()} | ${String(stdout || "").trim()}`));
|
||||
else resolve(String(stdout || "").trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sincronizza le skill sul repo remoto: pull --ff-only → add skills/ → commit
|
||||
* (solo se ci sono modifiche) → push. Mai rebase/merge automatici.
|
||||
*/
|
||||
async function syncSkills(root: string, message?: string): Promise<string> {
|
||||
if (!existsSync(join(root, ".git"))) {
|
||||
throw new Error("Il package non è un clone git: sincronizzazione non disponibile.");
|
||||
}
|
||||
await gitExec(root, ["pull", "--ff-only", "-q"], 20_000);
|
||||
await gitExec(root, ["add", "-A"]);
|
||||
let committed = "";
|
||||
try {
|
||||
committed = await gitExec(root, [
|
||||
"-c", "user.name=enne2", "-c", "user.email=enne2@git.enne2.net",
|
||||
"commit", "-m", message || `skill-hub: aggiornamento skill (${new Date().toISOString().slice(0, 16)})`,
|
||||
], 20_000);
|
||||
} catch (err) {
|
||||
const msg = String((err as Error).message);
|
||||
if (!/nothing to commit|no changes added/i.test(msg)) throw err;
|
||||
committed = "nessuna modifica da committare";
|
||||
}
|
||||
const push = committed.includes("nessuna modifica") ? "push saltato (nessun commit)" : await gitExec(root, ["push", "origin", "main"], 30_000);
|
||||
return `pull --ff-only OK; ${committed}; push origin main OK` + (push ? ` (${push.slice(0, 120)})` : "");
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
|
||||
@@ -263,6 +297,7 @@ export default function (pi: ExtensionAPI) {
|
||||
"## Skill Hub (pi-skill-hub)",
|
||||
`Skill condivise installate (${names.length}): ${names.join(", ")}.`,
|
||||
"Se il task richiede una competenza operativa specializzata e nessuna skill nota corrisponde, esegui skill_search prima di improvvisare; carica la skill scelta con /skill:<name> (o leggi il suo SKILL.md per il corpo completo). Le evidenze di esecuzione vanno registrate in qmem.",
|
||||
"Per creare o modificare una skill: crea/aggiorna skills/<nome-kebab-case>/ nel package (SKILL.md con frontmatter name+description routing, references/ per i dettagli, scripts/ senza segreti né path macchina-specifici; SKILL.md ≤ 500 righe), poi sincronizza col tool skill_sync (pull --ff-only + add/commit/push su origin main; su conflitto ferma e chiedi all'utente). Le altre macchine ricevono col pull automatico all'avvio di sessione o con pi update --extensions. Guida completa: docs/AGGIUNGERE-SKILL.md nel package.",
|
||||
].join("\n");
|
||||
return { systemPrompt: `${event.systemPrompt}\n\n${block}` };
|
||||
});
|
||||
@@ -300,6 +335,31 @@ export default function (pi: ExtensionAPI) {
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "skill_sync",
|
||||
label: "Skill sync",
|
||||
description:
|
||||
"Sincronizza le skill condivise sul repo Git remoto (git.enne2.net/enne2/pi-skill-hub): pull --ff-only, add/commit di skills/, push su origin main. Usalo DOPO aver creato o modificato una skill (skills/<nome>/) per distribuirla alle altre macchine. Su conflitto/rete fallisce con messaggio: non fare rebase automatici, chiedi all'utente.",
|
||||
parameters: Type.Object({
|
||||
message: Type.Optional(Type.String({ description: "Messaggio di commit (default: skill-hub: aggiornamento skill <data>" })),
|
||||
}),
|
||||
async execute(_id, params) {
|
||||
try {
|
||||
const out = await syncSkills(PACKAGE_ROOT, params.message);
|
||||
const idx = buildIndex(true);
|
||||
return {
|
||||
content: [{ type: "text", text: `Skill Hub sincronizzato. ${out}\nSkill indicizzate: ${idx.entries.map((e) => e.name).join(", ")}` }],
|
||||
details: { synced: true, skills: idx.entries.map((e) => e.name) },
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Sync fallita: ${(err as Error).message}\nNon fare rebase/merge automatici: risolvere con l'utente.` }],
|
||||
details: { synced: false, error: (err as Error).message },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerTool({
|
||||
name: "skill_info",
|
||||
label: "Skill info",
|
||||
@@ -332,13 +392,15 @@ export default function (pi: ExtensionAPI) {
|
||||
});
|
||||
|
||||
pi.registerCommand("skill-sync", {
|
||||
description: "Ricostruisce l'indice delle skill del Skill Hub",
|
||||
description: "Skill Hub: pull remoto + push delle skill + ricostruzione indice",
|
||||
handler: async (_name, ctx) => {
|
||||
const idx = buildIndex(true);
|
||||
ctx.ui.notify(
|
||||
`Skill Hub: ${idx.entries.length} skill indicizzate (${idx.entries.map((e) => e.name).join(", ")})`,
|
||||
"info",
|
||||
);
|
||||
try {
|
||||
const out = await syncSkills(PACKAGE_ROOT);
|
||||
ctx.ui.notify(`Skill Hub: ${out} — ${idx.entries.length} skill indicizzate`, "info");
|
||||
} catch (err) {
|
||||
ctx.ui.notify(`Skill Hub: sync fallita (${(err as Error).message})`, "warning");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user