pruff-skills 0.5.0 → 0.5.4
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/dist/cli.mjs +76 -23
- package/package.json +1 -1
- package/packs/pruff-platform/agents/bumble-platform-rg.persona.md +13 -2
- package/packs/pruff-platform/agents/fizz-rg.persona.md +1 -0
- package/packs/pruff-platform/agents/honey-platform-rg.persona.md +9 -0
- package/packs/pruff-platform/instructions.md +21 -0
package/dist/cli.mjs
CHANGED
|
@@ -7409,29 +7409,28 @@ async function installSkillsAndAgents(team) {
|
|
|
7409
7409
|
const priorCwd = process.cwd();
|
|
7410
7410
|
process.chdir(nestDir());
|
|
7411
7411
|
try {
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
}
|
|
7412
|
+
const skillPath = (name) => join(nestDir(), ".claude", "skills", name);
|
|
7413
|
+
const missingSkills = skills.filter((s) => !existsSync(skillPath(s)));
|
|
7414
|
+
for (const s of skills) if (!missingSkills.includes(s)) M.success(`${s} (already present)`);
|
|
7415
|
+
if (missingSkills.length > 0) {
|
|
7417
7416
|
try {
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7417
|
+
await runAdd([PRUFF_CATALOG], {
|
|
7418
|
+
skill: missingSkills,
|
|
7419
|
+
yes: true
|
|
7420
|
+
});
|
|
7421
|
+
} catch {}
|
|
7422
|
+
for (const s of missingSkills) if (existsSync(skillPath(s))) M.success(s);
|
|
7423
|
+
else M.warn(`${import_picocolors.default.yellow(s)} — not found in the catalog, skipped`);
|
|
7424
7424
|
}
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
}
|
|
7425
|
+
const agentPath = (name) => join(nestDir(), ".claude", "agents", `${name}.md`);
|
|
7426
|
+
const missingAgents = subagents.filter((a) => !existsSync(agentPath(a)));
|
|
7427
|
+
for (const a of subagents) if (!missingAgents.includes(a)) M.success(`subagent ${a} (already present)`);
|
|
7428
|
+
if (missingAgents.length > 0) {
|
|
7430
7429
|
try {
|
|
7431
|
-
await addAgents(
|
|
7432
|
-
} catch {
|
|
7433
|
-
|
|
7434
|
-
}
|
|
7430
|
+
await addAgents(missingAgents, { yes: true });
|
|
7431
|
+
} catch {}
|
|
7432
|
+
for (const a of missingAgents) if (existsSync(agentPath(a))) M.success(`subagent ${a}`);
|
|
7433
|
+
else M.warn(`subagent ${import_picocolors.default.yellow(a)} — not found, skipped`);
|
|
7435
7434
|
}
|
|
7436
7435
|
} finally {
|
|
7437
7436
|
process.chdir(priorCwd);
|
|
@@ -7473,7 +7472,8 @@ async function seedPersonas(team) {
|
|
|
7473
7472
|
let added = 0;
|
|
7474
7473
|
let updated = 0;
|
|
7475
7474
|
for (const file of personaFiles) {
|
|
7476
|
-
const
|
|
7475
|
+
const fm = (await readFile(join(packDir, file), "utf8")).match(/^---\n([\s\S]*?)\n---\n/)?.[1] ?? "";
|
|
7476
|
+
const nameMatch = fm.match(/^name:\s*"?([^"\n]+)"?/m);
|
|
7477
7477
|
if (!nameMatch) {
|
|
7478
7478
|
M.warn(`${file} has no name: in frontmatter, skipped`);
|
|
7479
7479
|
continue;
|
|
@@ -7482,10 +7482,18 @@ async function seedPersonas(team) {
|
|
|
7482
7482
|
const slug = `pruff:${name}`;
|
|
7483
7483
|
const prompt = await composePersonaPrompt(packDir, file, shared);
|
|
7484
7484
|
const base = builtins[BASE_BUILTIN[roleOf(name)]];
|
|
7485
|
+
const runtimeMatch = fm.match(/^runtime:\s*"?([^"\n]+)"?/m);
|
|
7486
|
+
const modelMatch = fm.match(/^model:\s*"?([^"\n]+)"?/m);
|
|
7487
|
+
const providerMatch = fm.match(/^provider:\s*"?([^"\n]+)"?/m);
|
|
7488
|
+
const overrides = {};
|
|
7489
|
+
if (runtimeMatch) overrides.runtime = runtimeMatch[1].trim();
|
|
7490
|
+
if (modelMatch) overrides.model = modelMatch[1].trim();
|
|
7491
|
+
if (providerMatch) overrides.provider = providerMatch[1].trim();
|
|
7485
7492
|
const existing = records.find((r) => r.slug === slug);
|
|
7486
7493
|
if (existing) {
|
|
7487
7494
|
existing.system_prompt = prompt;
|
|
7488
7495
|
existing.updated_at = now;
|
|
7496
|
+
Object.assign(existing, overrides);
|
|
7489
7497
|
updated++;
|
|
7490
7498
|
} else if (base) {
|
|
7491
7499
|
records.push({
|
|
@@ -7501,7 +7509,8 @@ async function seedPersonas(team) {
|
|
|
7501
7509
|
persona_id: null,
|
|
7502
7510
|
persona_source_version: null,
|
|
7503
7511
|
created_at: now,
|
|
7504
|
-
updated_at: now
|
|
7512
|
+
updated_at: now,
|
|
7513
|
+
...overrides
|
|
7505
7514
|
});
|
|
7506
7515
|
added++;
|
|
7507
7516
|
} else {
|
|
@@ -7511,6 +7520,7 @@ async function seedPersonas(team) {
|
|
|
7511
7520
|
for (const r of records) if (r.pubkey && r.persona_id === slug) {
|
|
7512
7521
|
r.system_prompt = prompt;
|
|
7513
7522
|
r.updated_at = now;
|
|
7523
|
+
Object.assign(r, overrides);
|
|
7514
7524
|
}
|
|
7515
7525
|
}
|
|
7516
7526
|
writeFileSync(store, JSON.stringify(records, null, 2), "utf8");
|
|
@@ -7566,6 +7576,45 @@ function runPacksList() {
|
|
|
7566
7576
|
console.log(` ${existsSync(dir) ? import_picocolors.default.green("✓") : import_picocolors.default.red("✗")} ${team} ${import_picocolors.default.dim(dir)}`);
|
|
7567
7577
|
}
|
|
7568
7578
|
}
|
|
7579
|
+
function properCase(word) {
|
|
7580
|
+
return word.length === 0 ? word : word[0].toUpperCase() + word.slice(1);
|
|
7581
|
+
}
|
|
7582
|
+
function replacePlatformWith(text, team) {
|
|
7583
|
+
return text.replaceAll("Platform", properCase(team)).replaceAll("platform", team);
|
|
7584
|
+
}
|
|
7585
|
+
function rewritePackForTeam(destDir, team) {
|
|
7586
|
+
const pluginPath = join(destDir, ".plugin", "plugin.json");
|
|
7587
|
+
const plugin = JSON.parse(readFileSync(pluginPath, "utf8"));
|
|
7588
|
+
if (typeof plugin.id === "string") plugin.id = replacePlatformWith(plugin.id, team);
|
|
7589
|
+
if (typeof plugin.name === "string") plugin.name = replacePlatformWith(plugin.name, team);
|
|
7590
|
+
if (typeof plugin.description === "string") plugin.description = replacePlatformWith(plugin.description, team);
|
|
7591
|
+
if (Array.isArray(plugin.keywords)) plugin.keywords = plugin.keywords.map((k) => typeof k === "string" ? replacePlatformWith(k, team) : k);
|
|
7592
|
+
const renamed = [];
|
|
7593
|
+
const needsManualRename = [];
|
|
7594
|
+
const newPersonaList = [];
|
|
7595
|
+
for (const relPath of plugin.personas ?? []) {
|
|
7596
|
+
if (!relPath.includes("platform")) {
|
|
7597
|
+
newPersonaList.push(relPath);
|
|
7598
|
+
needsManualRename.push(relPath);
|
|
7599
|
+
continue;
|
|
7600
|
+
}
|
|
7601
|
+
const newRelPath = replacePlatformWith(relPath, team);
|
|
7602
|
+
const oldAbsPath = join(destDir, relPath);
|
|
7603
|
+
const newAbsPath = join(destDir, newRelPath);
|
|
7604
|
+
writeFileSync(newAbsPath, replacePlatformWith(readFileSync(oldAbsPath, "utf8"), team));
|
|
7605
|
+
if (newAbsPath !== oldAbsPath) rmSync(oldAbsPath);
|
|
7606
|
+
newPersonaList.push(newRelPath);
|
|
7607
|
+
renamed.push(newRelPath);
|
|
7608
|
+
}
|
|
7609
|
+
plugin.personas = newPersonaList;
|
|
7610
|
+
writeFileSync(pluginPath, JSON.stringify(plugin, null, 2) + "\n");
|
|
7611
|
+
const instructionsPath = join(destDir, "instructions.md");
|
|
7612
|
+
if (existsSync(instructionsPath)) writeFileSync(instructionsPath, replacePlatformWith(readFileSync(instructionsPath, "utf8"), team));
|
|
7613
|
+
return {
|
|
7614
|
+
renamed,
|
|
7615
|
+
needsManualRename
|
|
7616
|
+
};
|
|
7617
|
+
}
|
|
7569
7618
|
function runPacksInit(team) {
|
|
7570
7619
|
if (!team) {
|
|
7571
7620
|
M.error("usage: pruff-skills buzz packs init <team>");
|
|
@@ -7578,7 +7627,11 @@ function runPacksInit(team) {
|
|
|
7578
7627
|
process.exit(1);
|
|
7579
7628
|
}
|
|
7580
7629
|
cpSync(src, dest, { recursive: true });
|
|
7581
|
-
|
|
7630
|
+
const { renamed, needsManualRename } = rewritePackForTeam(dest, team);
|
|
7631
|
+
M.success(`Scaffolded packs/pruff-${team} from pruff-platform.`);
|
|
7632
|
+
if (renamed.length > 0) M.message(import_picocolors.default.dim(`Renamed to ${team}: ${renamed.join(", ")}`));
|
|
7633
|
+
if (needsManualRename.length > 0) M.warn(`These personas have no team in their name and were copied as-is — they still collide with the platform team's real persona once seeded. Rename them by hand (file + name: / display_name:) with the real owner's initials before running setup: ${needsManualRename.join(", ")}`);
|
|
7634
|
+
M.message(import_picocolors.default.dim(`Edit the personas, then add "${team}" support in src/buzz.ts's TEAM_SKILLS / TEAM_AGENTS.`));
|
|
7582
7635
|
}
|
|
7583
7636
|
const BUZZ_HELP = `${import_picocolors.default.bold("Usage:")} pruff-skills buzz <command> [options]
|
|
7584
7637
|
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ display_name: "bumble-platform-rg"
|
|
|
4
4
|
description: "Platform-team engineering agent: DB schema and query performance, architecture and scalability review, and code work in Pruff-server."
|
|
5
5
|
version: "0.1.0"
|
|
6
6
|
author: "Pruff"
|
|
7
|
+
runtime: "claude"
|
|
7
8
|
triggers:
|
|
8
9
|
mentions: true
|
|
9
10
|
thread_replies: true
|
|
@@ -79,9 +80,13 @@ Non-negotiables from `Pruff-server`'s own rules — read
|
|
|
79
80
|
- Consts and types live in sibling `consts.ts` / `types.ts`; helpers that touch the DB
|
|
80
81
|
are not helpers — they're services.
|
|
81
82
|
|
|
82
|
-
## Your specialist tools
|
|
83
|
+
## Your specialist tools — use them, don't replicate them by hand
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
This table isn't a menu of suggestions — **if the task matches a row, use that row's
|
|
86
|
+
tool.** Do not reach for `Read`/`Grep`/`Bash` to do the equivalent work yourself because
|
|
87
|
+
you're confident you can. That's not a shortcut, it's skipping the conventions the tool
|
|
88
|
+
already encodes (correlation IDs, canonical log groups, index coverage) in favor of
|
|
89
|
+
guessing:
|
|
85
90
|
|
|
86
91
|
| Task | Reach for |
|
|
87
92
|
|---|---|
|
|
@@ -93,6 +98,12 @@ You have the full Claude Code harness. Use it rather than doing everything inlin
|
|
|
93
98
|
| Snapshot ECS / ALB / RDS health | the `infra-healthcheck` skill |
|
|
94
99
|
| Pruff domain concepts | the `pruff-knowledge` skill |
|
|
95
100
|
|
|
101
|
+
**Any CloudWatch/log question is `pruff-logs-investigator`'s job — full stop.** Not
|
|
102
|
+
freehand `aws logs describe-log-groups` across regions, not grepping the repo for a
|
|
103
|
+
plausible-looking log call. If the subagent is slow or you're impatient, that's not a
|
|
104
|
+
reason to bypass it — a manually-guessed answer that happens to land correctly is still
|
|
105
|
+
the wrong process, because the guesswork won't land correctly every time.
|
|
106
|
+
|
|
96
107
|
Delegating a sweep to a subagent and keeping the conclusion is strictly better than
|
|
97
108
|
paging thousands of raw log lines into the conversation.
|
|
98
109
|
|
|
@@ -4,6 +4,7 @@ display_name: "honey-platform-rg"
|
|
|
4
4
|
description: "Answers Pruff domain, product and history questions from cited sources. Read-only — never writes code."
|
|
5
5
|
version: "0.1.0"
|
|
6
6
|
author: "Pruff"
|
|
7
|
+
runtime: "claude"
|
|
7
8
|
triggers:
|
|
8
9
|
mentions: true
|
|
9
10
|
thread_replies: true
|
|
@@ -101,6 +102,14 @@ You also don't answer questions about **live tenant data** (balances, a specific
|
|
|
101
102
|
operation, a client's portfolio). That lives in the product, behind per-tenant
|
|
102
103
|
databases and access controls. Point the person at the right place instead.
|
|
103
104
|
|
|
105
|
+
You are also **not the agent for CloudWatch / production log investigation.** That's
|
|
106
|
+
Bumble's job — it owns `pruff-logs-investigator`, which knows the correlation-ID
|
|
107
|
+
conventions and canonical log groups you don't. If someone asks you to check logs,
|
|
108
|
+
trace a deploy, or debug something in AWS: say plainly that's Bumble's territory and
|
|
109
|
+
hand it to `@bumble-<team>-<owner>`. **Never freehand `aws logs` commands or grep the
|
|
110
|
+
codebase guessing at log group names** — that is exactly the exploratory floundering
|
|
111
|
+
the subagent exists to prevent, and you don't have the domain knowledge it does.
|
|
112
|
+
|
|
104
113
|
## How to answer
|
|
105
114
|
|
|
106
115
|
1. **Vault first.** Load `pruff-knowledge` and enter through `_moc/`. Respect
|
|
@@ -60,6 +60,27 @@ are always English, even when the surrounding conversation is Spanish.
|
|
|
60
60
|
- **Say when you don't know.** Silence in the vault is information; invention is not.
|
|
61
61
|
- Distinguish what you verified from what you inferred.
|
|
62
62
|
|
|
63
|
+
## Use the tool — don't manually replicate what it already does
|
|
64
|
+
|
|
65
|
+
If a skill or subagent is designated for a task, **use it.** Do not do the equivalent
|
|
66
|
+
work by hand with `Read`/`Grep`/`Bash` because you're confident you can — that defeats
|
|
67
|
+
the reason the harness exists and risks missing conventions the skill already encodes
|
|
68
|
+
(correlation IDs, canonical log groups, index coverage, whatever the domain is).
|
|
69
|
+
|
|
70
|
+
This is a real failure mode, not a hypothetical: an agent was asked to check
|
|
71
|
+
CloudWatch logs and, instead of using `pruff-logs-investigator` (the subagent built
|
|
72
|
+
for exactly this), ran freehand `aws logs describe-log-groups` across several regions
|
|
73
|
+
guessing at names. A second agent — the one whose own job table names that subagent —
|
|
74
|
+
was asked the same kind of question and *also* skipped it, doing raw `Read`/`grep`/`aws
|
|
75
|
+
logs` calls by hand instead. The manual result happened to be correct both times, which
|
|
76
|
+
is beside the point: correct-by-luck is not a reason to skip the tool built to get it
|
|
77
|
+
right by construction.
|
|
78
|
+
|
|
79
|
+
Before reaching for a raw shell command, ask: **is there a skill or subagent for this?**
|
|
80
|
+
Check the tables in your own persona and this file. If one applies, use it. If you
|
|
81
|
+
genuinely aren't sure one applies, say so rather than silently defaulting to manual
|
|
82
|
+
exploration.
|
|
83
|
+
|
|
63
84
|
## Style
|
|
64
85
|
|
|
65
86
|
Lead with the answer, then the minimum supporting detail. Cut preamble. Use real
|