@papi-ai/skills 0.1.0
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/README.md +53 -0
- package/bin/install.mjs +104 -0
- package/lib/manifest.d.ts +33 -0
- package/lib/manifest.mjs +102 -0
- package/manifest.json +52 -0
- package/package.json +47 -0
- package/skills/check-mcp/SKILL.md +40 -0
- package/skills/deployment-completeness-audit/SKILL.md +255 -0
- package/skills/papi-advanced/SKILL.md +28 -0
- package/skills/papi-build/SKILL.md +52 -0
- package/skills/papi-idea/SKILL.md +37 -0
- package/skills/papi-plan/SKILL.md +164 -0
- package/skills/papi-strategy/SKILL.md +28 -0
- package/skills/playwright-skill/API_REFERENCE.md +653 -0
- package/skills/playwright-skill/EXAMPLES.md +166 -0
- package/skills/playwright-skill/SKILL.md +147 -0
- package/skills/playwright-skill/lib/helpers.js +441 -0
- package/skills/playwright-skill/package.json +26 -0
- package/skills/playwright-skill/run.js +228 -0
- package/skills/pr-reviewer/SKILL.md +443 -0
- package/skills/pr-reviewer/references/gh_cli_guide.md +368 -0
- package/skills/pr-reviewer/references/review_criteria.md +345 -0
- package/skills/pr-reviewer/references/scenarios.md +71 -0
- package/skills/pr-reviewer/references/troubleshooting.md +55 -0
- package/skills/pr-reviewer/scripts/add_inline_comment.py +163 -0
- package/skills/pr-reviewer/scripts/fetch_pr_data.py +327 -0
- package/skills/pr-reviewer/scripts/generate_review_files.py +480 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @papi-ai/skills
|
|
2
|
+
|
|
3
|
+
The canonical, versioned bundle of PAPI's shareable Claude Code skills. Projects
|
|
4
|
+
**install** this package instead of copying skill files, so they pick up fixes by
|
|
5
|
+
bumping a pinned version rather than silently drifting from forked copies.
|
|
6
|
+
|
|
7
|
+
## What's in the bundle
|
|
8
|
+
|
|
9
|
+
Nine ship-ready skills (the PAPI-internal skills — `bootstrap-project`,
|
|
10
|
+
`friction-promote`, `patch-notes`, `support-debug` — are intentionally **not** shipped):
|
|
11
|
+
|
|
12
|
+
**Generic engineering (eager):** `check-mcp`, `pr-reviewer`,
|
|
13
|
+
`deployment-completeness-audit`, `playwright-skill`
|
|
14
|
+
|
|
15
|
+
**Cycle methodology (lazy / trigger-loaded):** `papi-plan`, `papi-build`,
|
|
16
|
+
`papi-idea`, `papi-strategy`, `papi-advanced`
|
|
17
|
+
|
|
18
|
+
The authoritative inventory + checksums live in [`manifest.json`](./manifest.json),
|
|
19
|
+
regenerated from the skill content on `prepack`.
|
|
20
|
+
|
|
21
|
+
## Install into a project
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @papi-ai/skills install /path/to/project # symlink (copy fallback)
|
|
25
|
+
npx @papi-ai/skills install /path/to/project --copy # force copy, no symlinks
|
|
26
|
+
npx @papi-ai/skills install /path/to/project --force # overwrite a diverged skill
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each packaged skill is symlinked into `<project>/.claude/skills/<name>`. Where
|
|
30
|
+
symlinks are unsupported (some Windows / restricted filesystems) it copies instead.
|
|
31
|
+
|
|
32
|
+
## Pinned versioning
|
|
33
|
+
|
|
34
|
+
Published as `@papi-ai/skills@0.1.0`. A project pinned to `0.1.0` keeps that exact
|
|
35
|
+
content when `0.2.0` ships — upgrades are explicit (`npm i @papi-ai/skills@latest`
|
|
36
|
+
then re-run the installer), never silent.
|
|
37
|
+
|
|
38
|
+
## Local overrides
|
|
39
|
+
|
|
40
|
+
A skill placed in `<project>/.claude/skills.local/<name>` is an intentional
|
|
41
|
+
override: the installer never touches it and PAPI's stale-fork detection never
|
|
42
|
+
flags it. Use this to customise a skill without fighting the package.
|
|
43
|
+
|
|
44
|
+
## Stale-fork detection
|
|
45
|
+
|
|
46
|
+
`orient` (with `deep_housekeeping: true`) compares the project's `.claude/skills/`
|
|
47
|
+
against this manifest and surfaces any skill whose content has diverged from the
|
|
48
|
+
pinned registry, offering replacement. It prompts — it never auto-overwrites.
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { detectStaleForks } from '@papi-ai/skills/manifest';
|
|
52
|
+
const forks = detectStaleForks('/path/to/project'); // [{ name, packagedChecksum, localChecksum }]
|
|
53
|
+
```
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @papi-ai/skills installer.
|
|
3
|
+
//
|
|
4
|
+
// Installs the pinned skill bundle into a project's .claude/skills/ by
|
|
5
|
+
// symlinking each packaged skill (with a copy fallback where symlinks are
|
|
6
|
+
// unsupported, e.g. some Windows / restricted filesystems).
|
|
7
|
+
//
|
|
8
|
+
// Precedence: a skill present in <project>/.claude/skills.local/ is an
|
|
9
|
+
// intentional override and is never touched. An already-installed skill whose
|
|
10
|
+
// content has diverged is NOT clobbered unless --force is passed.
|
|
11
|
+
//
|
|
12
|
+
// Usage:
|
|
13
|
+
// papi-skills install <project-dir> [--copy] [--force] [--quiet]
|
|
14
|
+
// node bin/install.mjs <project-dir> [--copy] [--force] [--quiet]
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, cpSync, realpathSync,
|
|
18
|
+
} from 'node:fs';
|
|
19
|
+
import { join, resolve } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { loadManifest, listPackagedSkills, checksumSkillDir, PACKAGE_ROOT } from '../lib/manifest.mjs';
|
|
22
|
+
|
|
23
|
+
function main(argv) {
|
|
24
|
+
const args = argv.slice(2);
|
|
25
|
+
if (args[0] === 'install') args.shift(); // tolerate the verb form
|
|
26
|
+
const flags = new Set(args.filter((a) => a.startsWith('--')));
|
|
27
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
28
|
+
const projectDir = resolve(positional[0] ?? process.cwd());
|
|
29
|
+
const forceCopy = flags.has('--copy');
|
|
30
|
+
const force = flags.has('--force');
|
|
31
|
+
const quiet = flags.has('--quiet');
|
|
32
|
+
|
|
33
|
+
const manifest = loadManifest();
|
|
34
|
+
const skillsDir = join(projectDir, '.claude', 'skills');
|
|
35
|
+
const overrideDir = join(projectDir, '.claude', 'skills.local');
|
|
36
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
const log = quiet ? () => {} : (m) => console.log(m);
|
|
39
|
+
const summary = { linked: 0, copied: 0, upToDate: 0, overridden: 0, diverged: 0 };
|
|
40
|
+
|
|
41
|
+
for (const name of listPackagedSkills()) {
|
|
42
|
+
const src = join(PACKAGE_ROOT, 'skills', name);
|
|
43
|
+
const dest = join(skillsDir, name);
|
|
44
|
+
|
|
45
|
+
if (existsSync(join(overrideDir, name))) {
|
|
46
|
+
summary.overridden++;
|
|
47
|
+
log(` ↪ ${name} — skipped (local override in .claude/skills.local/)`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (existsSync(dest)) {
|
|
52
|
+
const st = lstatSync(dest);
|
|
53
|
+
if (st.isSymbolicLink()) {
|
|
54
|
+
// Already a managed symlink — refresh if it points elsewhere.
|
|
55
|
+
try {
|
|
56
|
+
if (realpathSync(dest) === realpathSync(src)) { summary.upToDate++; log(` = ${name} — up to date (linked)`); continue; }
|
|
57
|
+
} catch { /* dangling link — fall through to re-link */ }
|
|
58
|
+
} else if (st.isDirectory()) {
|
|
59
|
+
const matches = checksumSkillDir(dest) === skillEntry(manifest, name)?.checksum;
|
|
60
|
+
if (matches) { summary.upToDate++; log(` = ${name} — up to date`); continue; }
|
|
61
|
+
if (!force) {
|
|
62
|
+
summary.diverged++;
|
|
63
|
+
log(` ! ${name} — diverged from registry; not overwritten. Move to .claude/skills.local/ to keep, or re-run with --force.`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
rmSync(dest, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (forceCopy) {
|
|
71
|
+
cpSync(src, dest, { recursive: true });
|
|
72
|
+
summary.copied++;
|
|
73
|
+
log(` + ${name} — copied`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
symlinkSync(src, dest, 'dir');
|
|
79
|
+
summary.linked++;
|
|
80
|
+
log(` → ${name} — linked`);
|
|
81
|
+
} catch {
|
|
82
|
+
// Symlinks unsupported on this platform/filesystem — copy instead.
|
|
83
|
+
cpSync(src, dest, { recursive: true });
|
|
84
|
+
summary.copied++;
|
|
85
|
+
log(` + ${name} — copied (symlink unsupported)`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
log(
|
|
90
|
+
`\n@papi-ai/skills@${manifest.packageVersion} → ${skillsDir}\n` +
|
|
91
|
+
` linked ${summary.linked} · copied ${summary.copied} · up-to-date ${summary.upToDate} · ` +
|
|
92
|
+
`overridden ${summary.overridden} · diverged ${summary.diverged}`,
|
|
93
|
+
);
|
|
94
|
+
if (summary.diverged > 0 && !force) process.exitCode = 0; // informational, not a failure
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function skillEntry(manifest, name) {
|
|
98
|
+
return manifest.skills.find((s) => s.name === name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Run only when invoked directly, not when imported.
|
|
102
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
103
|
+
main(process.argv);
|
|
104
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Hand-written types for the ESM helpers in manifest.mjs.
|
|
2
|
+
// Lets TypeScript consumers (the MCP server's orient path) import with types.
|
|
3
|
+
|
|
4
|
+
export const PACKAGE_ROOT: string;
|
|
5
|
+
export const MANIFEST_SCHEMA_VERSION: number;
|
|
6
|
+
|
|
7
|
+
export interface SkillManifestEntry {
|
|
8
|
+
/** Skill directory name, e.g. "pr-reviewer". */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Where the skill is loaded from in a project. */
|
|
11
|
+
kind: 'eager' | 'lazy';
|
|
12
|
+
/** Short, stable content checksum of the packaged skill directory. */
|
|
13
|
+
checksum: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SkillsManifest {
|
|
17
|
+
schemaVersion: number;
|
|
18
|
+
/** Pinned package version, e.g. "0.1.0". */
|
|
19
|
+
packageVersion: string;
|
|
20
|
+
generatedAt: string;
|
|
21
|
+
skills: SkillManifestEntry[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface StaleFork {
|
|
25
|
+
name: string;
|
|
26
|
+
packagedChecksum: string;
|
|
27
|
+
localChecksum: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function checksumSkillDir(skillDir: string): string;
|
|
31
|
+
export function loadManifest(packageRoot?: string): SkillsManifest;
|
|
32
|
+
export function listPackagedSkills(packageRoot?: string): string[];
|
|
33
|
+
export function detectStaleForks(projectDir: string, packageRoot?: string): StaleFork[];
|
package/lib/manifest.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @papi-ai/skills — shared manifest helpers.
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for: the manifest schema, version comparison, and
|
|
4
|
+
// stale-fork detection. Both consumers import from here so the schema is
|
|
5
|
+
// defined exactly once (bin/install.mjs and the server's orient path).
|
|
6
|
+
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
/** Package root = parent of this lib/ directory. */
|
|
14
|
+
export const PACKAGE_ROOT = dirname(HERE);
|
|
15
|
+
|
|
16
|
+
export const MANIFEST_SCHEMA_VERSION = 1;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Compute a stable content checksum for a single skill directory.
|
|
20
|
+
* Hashes every file's relative path + bytes in sorted order so the result is
|
|
21
|
+
* deterministic across machines and independent of filesystem ordering.
|
|
22
|
+
*/
|
|
23
|
+
export function checksumSkillDir(skillDir) {
|
|
24
|
+
const hash = createHash('sha256');
|
|
25
|
+
const files = [];
|
|
26
|
+
walk(skillDir, skillDir, files);
|
|
27
|
+
files.sort();
|
|
28
|
+
for (const rel of files) {
|
|
29
|
+
hash.update(rel);
|
|
30
|
+
hash.update('\0');
|
|
31
|
+
hash.update(readFileSync(join(skillDir, rel)));
|
|
32
|
+
hash.update('\0');
|
|
33
|
+
}
|
|
34
|
+
return hash.digest('hex').slice(0, 16);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function walk(root, dir, out) {
|
|
38
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
+
// Never let machine-local cruft influence the checksum.
|
|
40
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
41
|
+
if (entry.name.startsWith('.temp-execution-')) continue;
|
|
42
|
+
if (entry.name === 'package-lock.json') continue;
|
|
43
|
+
const abs = join(dir, entry.name);
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
walk(root, abs, out);
|
|
46
|
+
} else if (entry.isFile()) {
|
|
47
|
+
out.push(abs.slice(root.length + 1));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Load and parse the package manifest. Throws if missing/malformed. */
|
|
53
|
+
export function loadManifest(packageRoot = PACKAGE_ROOT) {
|
|
54
|
+
const raw = readFileSync(join(packageRoot, 'manifest.json'), 'utf8');
|
|
55
|
+
const manifest = JSON.parse(raw);
|
|
56
|
+
if (manifest.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`@papi-ai/skills manifest schemaVersion ${manifest.schemaVersion} != expected ${MANIFEST_SCHEMA_VERSION}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return manifest;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** List the skill names that ship in the package. */
|
|
65
|
+
export function listPackagedSkills(packageRoot = PACKAGE_ROOT) {
|
|
66
|
+
return loadManifest(packageRoot).skills.map((s) => s.name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect skill forks in a project that have drifted from the packaged registry.
|
|
71
|
+
*
|
|
72
|
+
* For each packaged skill, if the project has a same-named skill under
|
|
73
|
+
* .claude/skills/ whose content checksum differs from the manifest, it is
|
|
74
|
+
* reported as a stale fork. Skills under .claude/skills.local/ are intentional
|
|
75
|
+
* overrides and are never reported.
|
|
76
|
+
*
|
|
77
|
+
* Fail-soft: returns [] on any filesystem error so callers (e.g. orient) never
|
|
78
|
+
* break on a missing or partial project layout.
|
|
79
|
+
*
|
|
80
|
+
* @returns {Array<{ name: string, packagedChecksum: string, localChecksum: string }>}
|
|
81
|
+
*/
|
|
82
|
+
export function detectStaleForks(projectDir, packageRoot = PACKAGE_ROOT) {
|
|
83
|
+
try {
|
|
84
|
+
const manifest = loadManifest(packageRoot);
|
|
85
|
+
const skillsDir = join(projectDir, '.claude', 'skills');
|
|
86
|
+
const localOverrideDir = join(projectDir, '.claude', 'skills.local');
|
|
87
|
+
const stale = [];
|
|
88
|
+
for (const skill of manifest.skills) {
|
|
89
|
+
const localPath = join(skillsDir, skill.name);
|
|
90
|
+
if (!existsSync(localPath) || !statSync(localPath).isDirectory()) continue;
|
|
91
|
+
// Intentional override — never flag.
|
|
92
|
+
if (existsSync(join(localOverrideDir, skill.name))) continue;
|
|
93
|
+
const localChecksum = checksumSkillDir(localPath);
|
|
94
|
+
if (localChecksum !== skill.checksum) {
|
|
95
|
+
stale.push({ name: skill.name, packagedChecksum: skill.checksum, localChecksum });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return stale;
|
|
99
|
+
} catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
package/manifest.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"packageVersion": "0.1.0",
|
|
4
|
+
"generatedAt": "2026-05-25T12:07:27.744Z",
|
|
5
|
+
"skills": [
|
|
6
|
+
{
|
|
7
|
+
"name": "check-mcp",
|
|
8
|
+
"kind": "eager",
|
|
9
|
+
"checksum": "e5fe53aca9b35e1e"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "deployment-completeness-audit",
|
|
13
|
+
"kind": "eager",
|
|
14
|
+
"checksum": "3740d59f520cbe24"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "papi-advanced",
|
|
18
|
+
"kind": "lazy",
|
|
19
|
+
"checksum": "81cc81ee3f702979"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "papi-build",
|
|
23
|
+
"kind": "lazy",
|
|
24
|
+
"checksum": "bb0b7107ab8c6b08"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "papi-idea",
|
|
28
|
+
"kind": "lazy",
|
|
29
|
+
"checksum": "ae5da6393e14fcd5"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "papi-plan",
|
|
33
|
+
"kind": "lazy",
|
|
34
|
+
"checksum": "6b9a42f263fa409e"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "papi-strategy",
|
|
38
|
+
"kind": "lazy",
|
|
39
|
+
"checksum": "5b2c55da2b0f4545"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "playwright-skill",
|
|
43
|
+
"kind": "eager",
|
|
44
|
+
"checksum": "1e8dcd223b968a85"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "pr-reviewer",
|
|
48
|
+
"kind": "eager",
|
|
49
|
+
"checksum": "836d46ee9fc4b994"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@papi-ai/skills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PAPI's shareable Claude Code skill bundle — single source of truth, installed (not copied) into projects with pinned versioning",
|
|
5
|
+
"license": "Elastic-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./manifest": {
|
|
9
|
+
"types": "./lib/manifest.d.ts",
|
|
10
|
+
"default": "./lib/manifest.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./manifest.json": "./manifest.json"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"papi-skills": "./bin/install.mjs"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"skills",
|
|
19
|
+
"lib/manifest.mjs",
|
|
20
|
+
"lib/manifest.d.ts",
|
|
21
|
+
"bin/install.mjs",
|
|
22
|
+
"manifest.json",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build:manifest": "node scripts/build-manifest.mjs",
|
|
27
|
+
"prepack": "node scripts/build-manifest.mjs",
|
|
28
|
+
"test": "vitest run"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"papi",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"skills",
|
|
34
|
+
"ai-agents"
|
|
35
|
+
],
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/cathalos92/papi-ui.git",
|
|
39
|
+
"directory": "packages/skills"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"vitest": "^4.0.18"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: check-mcp
|
|
3
|
+
description: Verify the PAPI MCP server is running and healthy. Use when the user says "check mcp", "is papi running", "mcp status", or at the start of any session that needs PAPI tools. Also use proactively if PAPI MCP tools are expected but not appearing in the available tools list.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Check MCP Server Health
|
|
7
|
+
|
|
8
|
+
Run these steps in order and report the result clearly.
|
|
9
|
+
|
|
10
|
+
## Step 1: Read config
|
|
11
|
+
Read `.mcp.json` from the project root. Extract the server command, args, and env vars.
|
|
12
|
+
|
|
13
|
+
If `.mcp.json` doesn't exist, stop and report: "No MCP server configured. Create .mcp.json to connect PAPI."
|
|
14
|
+
|
|
15
|
+
## Step 2: Check build artifact
|
|
16
|
+
Verify the file referenced in `args` exists on disk (e.g., `ls <path>`).
|
|
17
|
+
|
|
18
|
+
If missing, report: "Server build artifact not found at <path>. Rebuild with: `npm run build --workspace=packages/adapter-md && npm run build --workspace=packages/server`"
|
|
19
|
+
|
|
20
|
+
## Step 3: Test-spawn the server
|
|
21
|
+
Run the server briefly with the configured env vars to check for startup crashes:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd <parent-dir-of-server> && <ENV_VARS> node <path-to-index.js> 2>&1 &; sleep 2; kill %1 2>/dev/null; wait 2>/dev/null
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- If it produces an error (like "Project root required"), report the exact error and suggest the fix.
|
|
28
|
+
- If no output (clean start), the server binary is healthy.
|
|
29
|
+
|
|
30
|
+
## Step 4: Check Claude Code connection
|
|
31
|
+
Look at the available tools list. If PAPI MCP tools (like `mcp__papi__*`) are present, the server is connected. If not, tell the user:
|
|
32
|
+
|
|
33
|
+
"PAPI MCP server binary is healthy but not connected to Claude Code. Reload the VS Code window (Cmd+Shift+P -> Developer: Reload Window) to reconnect."
|
|
34
|
+
|
|
35
|
+
## Step 5: Report
|
|
36
|
+
Summarize with one of:
|
|
37
|
+
- **Healthy**: Server configured, binary starts clean, tools available
|
|
38
|
+
- **Healthy but disconnected**: Binary works, but Claude Code needs a window reload
|
|
39
|
+
- **Misconfigured**: Explain what's wrong (wrong env var name, missing artifact, crash on start)
|
|
40
|
+
- **Not configured**: No .mcp.json found
|