@pharaoh-so/mcp 0.3.15 → 0.3.17
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/commands/plan.md +139 -2
- package/dist/install-skills.js +156 -5
- package/package.json +1 -1
package/commands/plan.md
CHANGED
|
@@ -1,5 +1,142 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Architecture-aware planning with Pharaoh reconnaissance
|
|
2
|
+
description: Architecture-aware planning with Pharaoh reconnaissance (adapted from Garry Tan's planning framework)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Plan Review
|
|
6
|
+
|
|
7
|
+
Architecture-aware plan review before implementation. Adapted from [Garry Tan's planning framework](https://www.youtube.com/watch?v=bMknfKXIFA8) for AI-assisted development.
|
|
8
|
+
|
|
9
|
+
**You are now in plan mode. Do NOT make any code changes. Think, evaluate, and present decisions.**
|
|
10
|
+
|
|
11
|
+
## Document Review
|
|
12
|
+
|
|
13
|
+
If the user provides a document, PRD, prompt, or artifact alongside this command, that IS the plan to review. Apply all review sections to that document. Do not treat it as background context — it is the subject of evaluation.
|
|
14
|
+
|
|
15
|
+
Still run Step 1 (Reconnaissance) even when reviewing a document — always verify the plan against the actual codebase state.
|
|
16
|
+
|
|
17
|
+
## Project Overrides
|
|
18
|
+
|
|
19
|
+
If a `.claude/plan-review.md` file exists in this project, read it now and apply those rules on top of this baseline. Project rules take precedence where they conflict.
|
|
20
|
+
|
|
21
|
+
## Engineering Preferences (guide all recommendations)
|
|
22
|
+
|
|
23
|
+
- DRY: flag repetition aggressively
|
|
24
|
+
- Well-tested: too many tests > too few; mutation score > line coverage
|
|
25
|
+
- "Engineered enough" — not fragile/hacky, not over-abstracted
|
|
26
|
+
- Handle more edge cases, not fewer; thoughtfulness > speed
|
|
27
|
+
- Explicit > clever; simple > complex
|
|
28
|
+
- Subtraction > addition; target zero or negative net LOC
|
|
29
|
+
- Every export must have a caller; unwired code doesn't exist
|
|
30
|
+
|
|
31
|
+
## Step 1: Pharaoh Reconnaissance (Required — do this BEFORE reviewing)
|
|
32
|
+
|
|
33
|
+
Do NOT review from memory or assumptions. Query the actual codebase first.
|
|
34
|
+
|
|
35
|
+
### If Pharaoh MCP tools are available:
|
|
36
|
+
|
|
37
|
+
1. `get_codebase_map` — current modules, hot files, dependency graph
|
|
38
|
+
2. `search_functions` for keywords related to the plan — find existing code to reuse/extend
|
|
39
|
+
3. `get_module_context` on affected modules — entry points, patterns, conventions
|
|
40
|
+
4. `query_dependencies` between affected modules — coupling, circular deps
|
|
41
|
+
5. `get_blast_radius` on the primary target of the change — know what breaks
|
|
42
|
+
6. `check_reachability` on the primary target — verify it's actually reachable from entry points before worrying about it
|
|
43
|
+
|
|
44
|
+
### Without Pharaoh (graceful fallback):
|
|
45
|
+
|
|
46
|
+
1. Search the codebase for files and functions related to the plan (grep, glob)
|
|
47
|
+
2. Read the entry points and module structure of affected areas
|
|
48
|
+
3. Check existing tests for the modules the plan will touch
|
|
49
|
+
4. Trace imports manually to estimate blast radius
|
|
50
|
+
|
|
51
|
+
Ground every recommendation in what actually exists. If you propose adding something, confirm it doesn't already exist. If you propose changing something, know its blast radius.
|
|
52
|
+
|
|
53
|
+
## Step 2: Mode Selection (MANDATORY — ask before proceeding)
|
|
54
|
+
|
|
55
|
+
**STOP and ask the user which mode before starting the review.** This is a hard gate — do not infer, assume, or skip this question even if the user says "yes", "go ahead", or "yes to all". Present both options and wait for an explicit choice:
|
|
56
|
+
|
|
57
|
+
> **BIG CHANGE or SMALL CHANGE?**
|
|
58
|
+
>
|
|
59
|
+
> - **BIG CHANGE**: Full interactive review, all relevant sections, up to 4 top issues per section
|
|
60
|
+
> - **SMALL CHANGE**: One question per section, only sections 2-4
|
|
61
|
+
|
|
62
|
+
If the user's response is ambiguous (e.g. "just do it", "yes to all"), ask again: "I need to know — BIG or SMALL change?" Do not proceed to Step 3 without an answer.
|
|
63
|
+
|
|
64
|
+
## Step 3: Review Sections
|
|
65
|
+
|
|
66
|
+
Adapt depth to change size. Skip sections that don't apply. **After each section, pause and ask for feedback before moving on.**
|
|
67
|
+
|
|
68
|
+
### Section 1 — Architecture (skip for small/single-file changes)
|
|
69
|
+
|
|
70
|
+
- Component boundaries and coupling concerns
|
|
71
|
+
- Dependency graph: does this change shrink or expand surface area?
|
|
72
|
+
- Data flow bottlenecks and single points of failure
|
|
73
|
+
- Does this need new code at all, or can a human process / existing pattern solve it?
|
|
74
|
+
|
|
75
|
+
### Section 2 — Code Quality (always)
|
|
76
|
+
|
|
77
|
+
- Organization, module structure, DRY violations (be aggressive)
|
|
78
|
+
- Error handling gaps and missing edge cases (call out explicitly)
|
|
79
|
+
- Technical debt: shortcuts, hardcoded values, magic strings
|
|
80
|
+
- Over-engineered or under-engineered relative to my preferences
|
|
81
|
+
- Reuse: does code for this already exist somewhere?
|
|
82
|
+
|
|
83
|
+
### Section 3 — Wiring & Integration (always)
|
|
84
|
+
|
|
85
|
+
- Are all new exports called from a production entry point?
|
|
86
|
+
- Run `get_blast_radius` on any new/changed functions — zero callers = not done
|
|
87
|
+
- `check_reachability` on new exports — verify reachable from API handlers, crons, or event handlers
|
|
88
|
+
- Does the plan declare WHERE new code gets called from? If not, flag it
|
|
89
|
+
- Integration points: how does this connect to what already exists?
|
|
90
|
+
|
|
91
|
+
### Section 4 — Tests (always)
|
|
92
|
+
|
|
93
|
+
- Coverage gaps: unit, integration, e2e
|
|
94
|
+
- Test quality: real assertions with hardcoded expected values, not `.toBeDefined()` or computed expectations
|
|
95
|
+
- Missing edge cases and untested failure/error paths
|
|
96
|
+
- One integration test proving wiring > ten isolated unit tests
|
|
97
|
+
|
|
98
|
+
### Section 5 — Performance (only if relevant)
|
|
99
|
+
|
|
100
|
+
- N+1 queries, unnecessary DB round-trips
|
|
101
|
+
- Memory concerns, caching opportunities
|
|
102
|
+
- Slow or high-complexity code paths
|
|
103
|
+
|
|
104
|
+
### Section 6 — Security & Attack Surface (always for new endpoints/routes/APIs; skip for pure refactors)
|
|
105
|
+
|
|
106
|
+
- **Authentication model** — what authenticates requests in this plan? Where is it validated? What happens on auth failure (redirect, 401, silent pass-through)? Use `search_functions` to find existing auth middleware and confirm reuse.
|
|
107
|
+
- **Sensitive data in URLs** — does the design put tokens, session IDs, or tenant identifiers in URL paths or query params? These leak via Referer headers, browser history, logs, and link sharing.
|
|
108
|
+
- **Authorization boundaries** — what prevents User A from accessing User B's data? Is there an ownership check, or just an "is logged in" check? Use `get_blast_radius` on existing ownership-check functions to see where they're already enforced.
|
|
109
|
+
- **Input trust boundary** — does the plan accept user input that flows into shell commands, database queries, HTML rendering, or file paths? Each is an injection vector.
|
|
110
|
+
- **Error and response surface** — will error responses or API payloads expose internals (stack traces, DB schemas, internal IDs) to unauthenticated callers?
|
|
111
|
+
- **New attack surface** — does the plan introduce new public URLs, webhooks, API routes, or WebSocket endpoints? Each needs: rate limiting, authentication, and input validation. Use `get_module_context` on the receiving module to check what protections exist.
|
|
112
|
+
|
|
113
|
+
## For Each Issue Found
|
|
114
|
+
|
|
115
|
+
For every specific issue (bug, smell, design concern, risk, missing wiring):
|
|
116
|
+
|
|
117
|
+
1. **Describe concretely** — file, line/function reference, what's wrong
|
|
118
|
+
2. **Present 2-3 options** including "do nothing" where reasonable
|
|
119
|
+
3. **For each option** — implementation effort, risk, blast radius, maintenance burden
|
|
120
|
+
4. **Recommend one** mapped to my preferences above, and say why
|
|
121
|
+
5. **Ask** whether I agree or want a different direction
|
|
122
|
+
|
|
123
|
+
Number each issue (1, 2, 3...) and letter each option (A, B, C...). Recommended option is always listed first. Use AskUserQuestion with clear labels like "Issue 1 Option A", "Issue 1 Option B".
|
|
124
|
+
|
|
125
|
+
## Pharaoh Checkpoints (use throughout, not just at the end)
|
|
126
|
+
|
|
127
|
+
- **Before reviewing**: recon (Step 1 above)
|
|
128
|
+
- **During review**: `get_blast_radius` when evaluating impact of changes; `search_functions` before suggesting new code
|
|
129
|
+
- **After decisions**: `check_reachability` on all new exports; `get_unused_code` to catch disconnections
|
|
130
|
+
- **Final sweep**: `get_blast_radius` on ALL new exports — zero callers on non-entry-points = plan is incomplete
|
|
131
|
+
|
|
132
|
+
## Workflow Rules
|
|
133
|
+
|
|
134
|
+
- **After each section, pause and ask for feedback before moving on**
|
|
135
|
+
- Do not assume priorities on timeline or scale
|
|
136
|
+
- If you see a better approach to the entire plan, say so BEFORE section-by-section review
|
|
137
|
+
- Challenge the approach if you see a better one — your job is to find problems I'll regret later
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
Here is the task to review:
|
|
141
|
+
|
|
142
|
+
$ARGUMENTS
|
package/dist/install-skills.js
CHANGED
|
@@ -31,6 +31,15 @@ const BUNDLED_COMMANDS_DIR = join(PKG_ROOT, "commands");
|
|
|
31
31
|
* get collision-safe distinctive names on this install path.
|
|
32
32
|
*/
|
|
33
33
|
const SKILL_PREFIX = "pharaoh-";
|
|
34
|
+
/**
|
|
35
|
+
* Filename of the install manifest written alongside the installed skills.
|
|
36
|
+
* Records which skills we installed in the previous run so the next run can
|
|
37
|
+
* diff against current source and remove skills that were cut or renamed
|
|
38
|
+
* between versions. See `cleanupRemovedSkills` for the diff logic.
|
|
39
|
+
*/
|
|
40
|
+
const MANIFEST_FILENAME = ".pharaoh-manifest.json";
|
|
41
|
+
/** Current manifest format version. Bump on breaking shape changes. */
|
|
42
|
+
const MANIFEST_VERSION = 1;
|
|
34
43
|
// ── Detection ───────────────────────────────────────────────────────
|
|
35
44
|
/**
|
|
36
45
|
* Detect whether Claude Code is installed by checking for ~/.claude/.
|
|
@@ -83,12 +92,25 @@ function installClaudeCodePlugin(home = homedir()) {
|
|
|
83
92
|
}
|
|
84
93
|
// Install skills under the pharaoh- prefix for collision-safe names.
|
|
85
94
|
const skillsDst = join(pluginDir, "skills");
|
|
95
|
+
// Read the previous install's manifest BEFORE copying fresh skills so
|
|
96
|
+
// the diff logic below sees the old state. Missing/corrupt manifest
|
|
97
|
+
// returns null → cleanupRemovedSkills becomes a no-op (first-run fallback).
|
|
98
|
+
const oldManifest = readPharaohManifest(skillsDst);
|
|
86
99
|
const skillCount = installPrefixedSkills(BUNDLED_SKILLS_DIR, skillsDst, SKILL_PREFIX);
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
100
|
+
// Legacy dual-layout cleanup: delete bare-name dirs matching CURRENT
|
|
101
|
+
// source skills. Still runs on every install because the manifest path
|
|
102
|
+
// doesn't know about bare-name stragglers for skills that are still in
|
|
103
|
+
// source — only about cuts.
|
|
91
104
|
cleanupBareNameSkills(BUNDLED_SKILLS_DIR, skillsDst);
|
|
105
|
+
// Manifest-based cut cleanup: for any skill that was in the previous
|
|
106
|
+
// install's manifest but is no longer in current source, remove both
|
|
107
|
+
// its bare-name and pharaoh-prefixed variants. Heals the token-burn
|
|
108
|
+
// from cut/renamed skills between versions (see #788).
|
|
109
|
+
cleanupRemovedSkills(BUNDLED_SKILLS_DIR, skillsDst, oldManifest);
|
|
110
|
+
// Stamp a fresh manifest reflecting exactly what's in current source.
|
|
111
|
+
// Written AFTER install + cleanup so it always matches on-disk state,
|
|
112
|
+
// even if a corrupt old manifest caused the diff step to skip.
|
|
113
|
+
writePharaohManifest(skillsDst, getSourceSkillNames(BUNDLED_SKILLS_DIR));
|
|
92
114
|
// Copy .mcp.json
|
|
93
115
|
const mcpSrc = join(PKG_ROOT, ".mcp.json");
|
|
94
116
|
if (existsSync(mcpSrc)) {
|
|
@@ -150,6 +172,10 @@ function installPrefixedSkills(srcDir, dstDir, prefix) {
|
|
|
150
172
|
* skills from other sources that happen to share the `pharaoh/skills/`
|
|
151
173
|
* install path).
|
|
152
174
|
*
|
|
175
|
+
* This is the LEGACY cleanup — it only heals the bare+prefixed dual
|
|
176
|
+
* layout for skills that are STILL in source. Skills cut between
|
|
177
|
+
* versions are handled by `cleanupRemovedSkills` via the manifest.
|
|
178
|
+
*
|
|
153
179
|
* @param srcDir - Bundled source skills directory.
|
|
154
180
|
* @param dstDir - Installed skills directory.
|
|
155
181
|
* @returns Number of stale directories removed.
|
|
@@ -171,6 +197,123 @@ function cleanupBareNameSkills(srcDir, dstDir) {
|
|
|
171
197
|
}
|
|
172
198
|
return removed;
|
|
173
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Enumerate the bare source skill names by listing the source skills dir.
|
|
202
|
+
* Returns the names that `installPrefixedSkills` would copy (directory
|
|
203
|
+
* entries that contain a `SKILL.md`). Used by both the manifest writer
|
|
204
|
+
* and the cleanup diff logic so the two stay in lockstep.
|
|
205
|
+
*
|
|
206
|
+
* @param srcDir - Bundled source skills directory.
|
|
207
|
+
* @returns Sorted array of bare skill dir names.
|
|
208
|
+
*/
|
|
209
|
+
function getSourceSkillNames(srcDir) {
|
|
210
|
+
if (!existsSync(srcDir))
|
|
211
|
+
return [];
|
|
212
|
+
const names = [];
|
|
213
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
214
|
+
if (!entry.isDirectory())
|
|
215
|
+
continue;
|
|
216
|
+
if (!existsSync(join(srcDir, entry.name, "SKILL.md")))
|
|
217
|
+
continue;
|
|
218
|
+
names.push(entry.name);
|
|
219
|
+
}
|
|
220
|
+
return names.sort();
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Read `.pharaoh-manifest.json` from the installed skills directory.
|
|
224
|
+
*
|
|
225
|
+
* Returns `null` when:
|
|
226
|
+
* - The file does not exist (first run after this fix shipped)
|
|
227
|
+
* - The file is corrupted or not valid JSON (treated as missing, caller
|
|
228
|
+
* will write a fresh one on the next install cycle)
|
|
229
|
+
*
|
|
230
|
+
* Corrupted-manifest handling is silent-but-logged: we don't want a bad
|
|
231
|
+
* manifest to crash the install, but we do want a warning on stderr so
|
|
232
|
+
* operators can spot persistent corruption.
|
|
233
|
+
*
|
|
234
|
+
* @param skillsDir - Installed skills directory.
|
|
235
|
+
* @returns The parsed manifest, or `null` if missing/invalid.
|
|
236
|
+
*/
|
|
237
|
+
function readPharaohManifest(skillsDir) {
|
|
238
|
+
const manifestPath = join(skillsDir, MANIFEST_FILENAME);
|
|
239
|
+
if (!existsSync(manifestPath))
|
|
240
|
+
return null;
|
|
241
|
+
try {
|
|
242
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
243
|
+
const parsed = JSON.parse(raw);
|
|
244
|
+
// Defensive shape check — a valid-JSON manifest with the wrong
|
|
245
|
+
// shape is as useful as no manifest, so we reject it and let the
|
|
246
|
+
// caller write a fresh one.
|
|
247
|
+
if (typeof parsed.version !== "number" ||
|
|
248
|
+
typeof parsed.installedAt !== "string" ||
|
|
249
|
+
!Array.isArray(parsed.skills) ||
|
|
250
|
+
!parsed.skills.every((s) => typeof s === "string")) {
|
|
251
|
+
process.stderr.write(`Pharaoh: ${MANIFEST_FILENAME} has unexpected shape — treating as missing and writing a fresh one.\n`);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return parsed;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
process.stderr.write(`Pharaoh: ${MANIFEST_FILENAME} is not valid JSON — treating as missing and writing a fresh one.\n`);
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Write a fresh manifest file listing the current install's bare skill
|
|
263
|
+
* names. Overwrites any existing manifest. The caller is responsible for
|
|
264
|
+
* ensuring the parent directory exists — `installPrefixedSkills` already
|
|
265
|
+
* does that as part of its own run, so writing the manifest after the
|
|
266
|
+
* install copy completes is safe.
|
|
267
|
+
*
|
|
268
|
+
* @param skillsDir - Installed skills directory.
|
|
269
|
+
* @param skills - Bare skill names present in the current source.
|
|
270
|
+
*/
|
|
271
|
+
function writePharaohManifest(skillsDir, skills) {
|
|
272
|
+
const manifest = {
|
|
273
|
+
version: MANIFEST_VERSION,
|
|
274
|
+
installedAt: new Date().toISOString(),
|
|
275
|
+
skills: [...skills],
|
|
276
|
+
};
|
|
277
|
+
writeFileSync(join(skillsDir, MANIFEST_FILENAME), JSON.stringify(manifest, null, "\t"));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Diff-based cleanup of skills removed between versions.
|
|
281
|
+
*
|
|
282
|
+
* Reads the old manifest's skill list and removes any entry that's no
|
|
283
|
+
* longer in the current source. For each removed name, we delete BOTH
|
|
284
|
+
* the bare-name and `pharaoh-` prefixed variants — the dual-layout from
|
|
285
|
+
* the pre-prefix era means both can exist on the same machine for the
|
|
286
|
+
* same skill, and leaving either behind burns context on every session.
|
|
287
|
+
*
|
|
288
|
+
* The flagship `pharaoh` skill is exempt from deletion even if a corrupt
|
|
289
|
+
* manifest lists it as removed. Losing the flagship would break every
|
|
290
|
+
* skill slash command, and the exempt-name guard is the only thing
|
|
291
|
+
* preventing an accidental self-destruct.
|
|
292
|
+
*
|
|
293
|
+
* @param srcDir - Bundled source skills directory.
|
|
294
|
+
* @param dstDir - Installed skills directory.
|
|
295
|
+
* @param oldManifest - Manifest from the previous install, or `null` for
|
|
296
|
+
* first-run-after-fix (in which case this is a no-op).
|
|
297
|
+
* @returns Number of directories removed.
|
|
298
|
+
*/
|
|
299
|
+
function cleanupRemovedSkills(srcDir, dstDir, oldManifest) {
|
|
300
|
+
if (!oldManifest || !existsSync(dstDir))
|
|
301
|
+
return 0;
|
|
302
|
+
const exemptName = SKILL_PREFIX.replace(/-$/, "");
|
|
303
|
+
const currentSkills = new Set(getSourceSkillNames(srcDir));
|
|
304
|
+
const removed = oldManifest.skills.filter((name) => !currentSkills.has(name) && name !== exemptName);
|
|
305
|
+
let deleted = 0;
|
|
306
|
+
for (const name of removed) {
|
|
307
|
+
for (const variant of [name, `${SKILL_PREFIX}${name}`]) {
|
|
308
|
+
const dir = join(dstDir, variant);
|
|
309
|
+
if (existsSync(dir)) {
|
|
310
|
+
rmSync(dir, { recursive: true, force: true });
|
|
311
|
+
deleted++;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return deleted;
|
|
316
|
+
}
|
|
174
317
|
/**
|
|
175
318
|
* Copy bundled slash command templates (`.md` files in the package's
|
|
176
319
|
* `commands/` directory) to `~/.claude/commands/`, unconditionally
|
|
@@ -275,10 +418,18 @@ export function installSkills(home = homedir()) {
|
|
|
275
418
|
if (!existsSync(targetDir)) {
|
|
276
419
|
mkdirSync(targetDir, { recursive: true });
|
|
277
420
|
}
|
|
421
|
+
// Read previous manifest before copying fresh skills (same rationale as
|
|
422
|
+
// the Claude Code path — diff the old state against current source so
|
|
423
|
+
// cut/renamed skills get cleaned up instead of lingering forever).
|
|
424
|
+
const oldManifest = readPharaohManifest(targetDir);
|
|
278
425
|
// Prefix skills for collision safety (same rationale as Claude Code path).
|
|
279
426
|
const count = installPrefixedSkills(BUNDLED_SKILLS_DIR, targetDir, SKILL_PREFIX);
|
|
280
|
-
//
|
|
427
|
+
// Legacy dual-layout cleanup for bare-name stragglers of current skills.
|
|
281
428
|
cleanupBareNameSkills(BUNDLED_SKILLS_DIR, targetDir);
|
|
429
|
+
// Manifest-based cut cleanup for skills removed between versions.
|
|
430
|
+
cleanupRemovedSkills(BUNDLED_SKILLS_DIR, targetDir, oldManifest);
|
|
431
|
+
// Stamp a fresh manifest reflecting current source.
|
|
432
|
+
writePharaohManifest(targetDir, getSourceSkillNames(BUNDLED_SKILLS_DIR));
|
|
282
433
|
return count;
|
|
283
434
|
}
|
|
284
435
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pharaoh-so/mcp",
|
|
3
3
|
"mcpName": "so.pharaoh/pharaoh",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.17",
|
|
5
5
|
"description": "MCP proxy for Pharaoh — maps codebases into queryable knowledge graphs for AI agents. Enables Claude Code in headless environments (VPS, SSH, CI) via device flow auth.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|