pan-wizard 2.8.1 → 2.9.1
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 +4 -2
- package/bin/install.js +23 -0
- package/commands/pan/assumptions.md +38 -3
- package/commands/pan/audit-deployment.md +6 -0
- package/commands/pan/debug.md +71 -2
- package/commands/pan/exec-phase.md +90 -0
- package/commands/pan/focus-auto.md +181 -18
- package/commands/pan/focus-design.md +302 -14
- package/commands/pan/focus-doc-audit.md +530 -0
- package/commands/pan/focus-drift-walking.md +525 -0
- package/commands/pan/focus-exec.md +168 -46
- package/commands/pan/focus-plan.md +204 -12
- package/commands/pan/focus-scan.md +17 -5
- package/commands/pan/map-codebase.md +32 -6
- package/commands/pan/milestone-audit.md +23 -0
- package/commands/pan/new-project.md +64 -0
- package/commands/pan/pause.md +42 -1
- package/commands/pan/plan-phase.md +84 -0
- package/commands/pan/profile.md +2 -1
- package/commands/pan/quick.md +15 -0
- package/commands/pan/resume.md +62 -2
- package/commands/pan/verify-phase.md +42 -0
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/commands.cjs +29 -7
- package/pan-wizard-core/bin/lib/config.cjs +10 -0
- package/pan-wizard-core/bin/lib/constants.cjs +3 -1
- package/pan-wizard-core/bin/lib/core.cjs +168 -21
- package/pan-wizard-core/bin/lib/focus.cjs +5 -0
- package/pan-wizard-core/bin/lib/verify.cjs +283 -4
- package/pan-wizard-core/bin/pan-tools.cjs +11 -2
- package/pan-wizard-core/references/model-profiles.md +191 -62
- package/pan-wizard-core/workflows/help.md +11 -1
- package/pan-wizard-core/workflows/profile.md +8 -1
- package/pan-wizard-core/workflows/settings.md +14 -0
- package/scripts/generate-skills-docs.py +560 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate PAN Wizard skills documentation from source command files.
|
|
4
|
+
|
|
5
|
+
Reads all shipped skills (commands/pan/*.md) and dev skills (.claude/commands/*.md),
|
|
6
|
+
parses their YAML frontmatter and content, and produces two documents:
|
|
7
|
+
|
|
8
|
+
docs/SKILLS-REFERENCE.md — Organized summary with tables, descriptions, tool matrix
|
|
9
|
+
docs/SKILLS-FULL-TEXT.md — Complete unabridged prompt text of every skill
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python scripts/generate-skills-docs.py # from repo root
|
|
13
|
+
python scripts/generate-skills-docs.py --dry-run # preview without writing
|
|
14
|
+
python scripts/generate-skills-docs.py --full-only # only generate SKILLS-FULL-TEXT.md
|
|
15
|
+
python scripts/generate-skills-docs.py --ref-only # only generate SKILLS-REFERENCE.md
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Configuration
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
SHIPPED_DIR = REPO_ROOT / "commands" / "pan"
|
|
30
|
+
DEV_DIR = REPO_ROOT / ".claude" / "commands"
|
|
31
|
+
PACKAGE_JSON = REPO_ROOT / "package.json"
|
|
32
|
+
OUT_REFERENCE = REPO_ROOT / "docs" / "SKILLS-REFERENCE.md"
|
|
33
|
+
OUT_FULL_TEXT = REPO_ROOT / "docs" / "SKILLS-FULL-TEXT.md"
|
|
34
|
+
|
|
35
|
+
# Group ordering for the reference doc (shipped skills)
|
|
36
|
+
GROUP_ORDER = [
|
|
37
|
+
"Getting Started",
|
|
38
|
+
"Phase Lifecycle",
|
|
39
|
+
"Phase Management",
|
|
40
|
+
"Focus",
|
|
41
|
+
"Milestone",
|
|
42
|
+
"Milestone Lifecycle",
|
|
43
|
+
"Session & Progress",
|
|
44
|
+
"System",
|
|
45
|
+
"Community",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Dev skill categorization (filename -> category)
|
|
49
|
+
DEV_CATEGORIES = {
|
|
50
|
+
"Development Workflow": [
|
|
51
|
+
"pandev", "execplan", "superplan", "featureAI", "review",
|
|
52
|
+
],
|
|
53
|
+
"Testing & Verification": [
|
|
54
|
+
"test", "quick", "pantest", "check", "check-platform", "auditai",
|
|
55
|
+
],
|
|
56
|
+
"Documentation & Audit": [
|
|
57
|
+
"doc-audit", "docs", "sync",
|
|
58
|
+
],
|
|
59
|
+
"Build & Deploy": [
|
|
60
|
+
"build", "run", "commit",
|
|
61
|
+
],
|
|
62
|
+
"Session Management": [
|
|
63
|
+
"session-start", "session-end",
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# All tools that shipped skills can reference
|
|
68
|
+
ALL_TOOLS = [
|
|
69
|
+
"Read", "Write", "Edit", "Bash", "Grep", "Glob",
|
|
70
|
+
"Agent", "Task", "TodoWrite", "AskUserQuestion",
|
|
71
|
+
"SlashCommand", "WebSearch", "WebFetch",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Frontmatter parser
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def parse_frontmatter(content: str) -> tuple[dict, str]:
|
|
79
|
+
"""Parse YAML frontmatter from markdown content.
|
|
80
|
+
|
|
81
|
+
Returns (metadata_dict, body_after_frontmatter).
|
|
82
|
+
If no frontmatter found, returns (empty dict, full content).
|
|
83
|
+
"""
|
|
84
|
+
if not content.startswith("---"):
|
|
85
|
+
return {}, content
|
|
86
|
+
|
|
87
|
+
end = content.find("---", 3)
|
|
88
|
+
if end == -1:
|
|
89
|
+
return {}, content
|
|
90
|
+
|
|
91
|
+
raw = content[3:end].strip()
|
|
92
|
+
body = content[end + 3:].strip()
|
|
93
|
+
meta = {}
|
|
94
|
+
|
|
95
|
+
current_key = None
|
|
96
|
+
current_list = None
|
|
97
|
+
|
|
98
|
+
for line in raw.split("\n"):
|
|
99
|
+
stripped = line.strip()
|
|
100
|
+
if not stripped:
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# List item under a key
|
|
104
|
+
if stripped.startswith("- ") and current_key:
|
|
105
|
+
if current_list is None:
|
|
106
|
+
current_list = []
|
|
107
|
+
current_list.append(stripped[2:].strip())
|
|
108
|
+
meta[current_key] = current_list
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# Key: value pair
|
|
112
|
+
if ":" in stripped:
|
|
113
|
+
# Save previous list if any
|
|
114
|
+
if current_list is not None:
|
|
115
|
+
current_list = None
|
|
116
|
+
|
|
117
|
+
colon_idx = stripped.index(":")
|
|
118
|
+
key = stripped[:colon_idx].strip()
|
|
119
|
+
value = stripped[colon_idx + 1:].strip()
|
|
120
|
+
|
|
121
|
+
current_key = key
|
|
122
|
+
|
|
123
|
+
if value:
|
|
124
|
+
# Strip quotes
|
|
125
|
+
if value.startswith('"') and value.endswith('"'):
|
|
126
|
+
value = value[1:-1]
|
|
127
|
+
elif value.startswith("'") and value.endswith("'"):
|
|
128
|
+
value = value[1:-1]
|
|
129
|
+
meta[key] = value
|
|
130
|
+
current_list = None
|
|
131
|
+
else:
|
|
132
|
+
# Value might be a list on following lines
|
|
133
|
+
current_list = []
|
|
134
|
+
meta[key] = current_list
|
|
135
|
+
|
|
136
|
+
return meta, body
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def extract_first_heading(body: str) -> str:
|
|
140
|
+
"""Extract the first # heading from the body."""
|
|
141
|
+
for line in body.split("\n"):
|
|
142
|
+
line = line.strip()
|
|
143
|
+
if line.startswith("# "):
|
|
144
|
+
return line[2:].strip()
|
|
145
|
+
return ""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Skill loading
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
class Skill:
|
|
153
|
+
def __init__(self, filepath: Path, is_dev: bool = False):
|
|
154
|
+
self.filepath = filepath
|
|
155
|
+
self.is_dev = is_dev
|
|
156
|
+
self.filename = filepath.stem # e.g., "focus-auto"
|
|
157
|
+
self.content = filepath.read_text(encoding="utf-8")
|
|
158
|
+
self.line_count = self.content.count("\n") + (1 if not self.content.endswith("\n") else 0)
|
|
159
|
+
self.meta, self.body = parse_frontmatter(self.content)
|
|
160
|
+
|
|
161
|
+
# Derive fields
|
|
162
|
+
if is_dev:
|
|
163
|
+
self.name = self.meta.get("name", f"/{self.filename}")
|
|
164
|
+
self.command = f"/{self.filename}"
|
|
165
|
+
else:
|
|
166
|
+
raw_name = self.meta.get("name", f"pan:{self.filename}")
|
|
167
|
+
# Normalize to /pan: prefix
|
|
168
|
+
if not raw_name.startswith("pan:") and not raw_name.startswith("/pan:"):
|
|
169
|
+
self.name = f"/pan:{raw_name}"
|
|
170
|
+
elif raw_name.startswith("pan:"):
|
|
171
|
+
self.name = f"/{raw_name}"
|
|
172
|
+
else:
|
|
173
|
+
self.name = raw_name
|
|
174
|
+
self.command = self.name
|
|
175
|
+
|
|
176
|
+
self.group = self.meta.get("group", "System")
|
|
177
|
+
self.description = self.meta.get("description", "")
|
|
178
|
+
raw_tools = self.meta.get("allowed-tools", [])
|
|
179
|
+
if isinstance(raw_tools, str):
|
|
180
|
+
# Handle comma-separated format: "Read, Write, Edit"
|
|
181
|
+
self.tools = [t.strip() for t in raw_tools.split(",") if t.strip()]
|
|
182
|
+
elif isinstance(raw_tools, list):
|
|
183
|
+
self.tools = raw_tools
|
|
184
|
+
else:
|
|
185
|
+
self.tools = []
|
|
186
|
+
self.argument_hint = self.meta.get("argument-hint", "")
|
|
187
|
+
self.heading = extract_first_heading(self.body)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def load_shipped_skills() -> list[Skill]:
|
|
191
|
+
"""Load all shipped skills from commands/pan/."""
|
|
192
|
+
if not SHIPPED_DIR.exists():
|
|
193
|
+
print(f"Warning: {SHIPPED_DIR} not found", file=sys.stderr)
|
|
194
|
+
return []
|
|
195
|
+
skills = []
|
|
196
|
+
for f in sorted(SHIPPED_DIR.glob("*.md")):
|
|
197
|
+
skills.append(Skill(f, is_dev=False))
|
|
198
|
+
return skills
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def load_dev_skills() -> list[Skill]:
|
|
202
|
+
"""Load all dev skills from .claude/commands/."""
|
|
203
|
+
if not DEV_DIR.exists():
|
|
204
|
+
print(f"Warning: {DEV_DIR} not found", file=sys.stderr)
|
|
205
|
+
return []
|
|
206
|
+
skills = []
|
|
207
|
+
for f in sorted(DEV_DIR.glob("*.md")):
|
|
208
|
+
skills.append(Skill(f, is_dev=True))
|
|
209
|
+
return skills
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_version() -> str:
|
|
213
|
+
"""Read version from package.json."""
|
|
214
|
+
try:
|
|
215
|
+
with open(PACKAGE_JSON, encoding="utf-8") as f:
|
|
216
|
+
return json.load(f)["version"]
|
|
217
|
+
except (FileNotFoundError, KeyError):
|
|
218
|
+
return "unknown"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# SKILLS-FULL-TEXT.md generator
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
def generate_full_text(shipped: list[Skill], dev: list[Skill], version: str) -> str:
|
|
226
|
+
"""Generate the complete full-text document."""
|
|
227
|
+
lines = []
|
|
228
|
+
w = lines.append
|
|
229
|
+
|
|
230
|
+
w("# PAN Wizard — Complete Skills Full Text")
|
|
231
|
+
w("")
|
|
232
|
+
w("Every skill (slash command) available in PAN Wizard, reproduced in full.")
|
|
233
|
+
w("This is the actual prompt text that Claude receives when a skill is invoked.")
|
|
234
|
+
w("")
|
|
235
|
+
w(f"**Version:** {version} | **Shipped:** {len(shipped)} skills | **Dev:** {len(dev)} skills | **Total:** {len(shipped) + len(dev)}")
|
|
236
|
+
w("")
|
|
237
|
+
w("> Auto-generated by `scripts/generate-skills-docs.py` — do not edit manually.")
|
|
238
|
+
w("")
|
|
239
|
+
w("---")
|
|
240
|
+
w("")
|
|
241
|
+
w(f"## Part 1: Shipped Skills ({len(shipped)})")
|
|
242
|
+
w("")
|
|
243
|
+
w("These are installed into host projects via the PAN installer.")
|
|
244
|
+
|
|
245
|
+
for skill in shipped:
|
|
246
|
+
w("")
|
|
247
|
+
w("---")
|
|
248
|
+
w("")
|
|
249
|
+
w(f"### {skill.command} ({skill.line_count} lines)")
|
|
250
|
+
w("")
|
|
251
|
+
w("```markdown")
|
|
252
|
+
w(skill.content.rstrip())
|
|
253
|
+
w("```")
|
|
254
|
+
w("")
|
|
255
|
+
|
|
256
|
+
w("")
|
|
257
|
+
w("---")
|
|
258
|
+
w("")
|
|
259
|
+
w(f"## Part 2: Dev Skills ({len(dev)})")
|
|
260
|
+
w("")
|
|
261
|
+
w("These exist only in the PAN source repository and are NOT shipped to end users.")
|
|
262
|
+
|
|
263
|
+
for skill in dev:
|
|
264
|
+
w("")
|
|
265
|
+
w("---")
|
|
266
|
+
w("")
|
|
267
|
+
w(f"### {skill.command} ({skill.line_count} lines)")
|
|
268
|
+
w("")
|
|
269
|
+
w("```markdown")
|
|
270
|
+
w(skill.content.rstrip())
|
|
271
|
+
w("```")
|
|
272
|
+
w("")
|
|
273
|
+
|
|
274
|
+
return "\n".join(lines)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
# SKILLS-REFERENCE.md generator
|
|
279
|
+
# ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
def group_skills_by(skills: list[Skill]) -> dict[str, list[Skill]]:
|
|
282
|
+
"""Group shipped skills by their group field."""
|
|
283
|
+
groups: dict[str, list[Skill]] = {}
|
|
284
|
+
for s in skills:
|
|
285
|
+
groups.setdefault(s.group, []).append(s)
|
|
286
|
+
return groups
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def dev_category_for(filename: str) -> str:
|
|
290
|
+
"""Find which dev category a filename belongs to."""
|
|
291
|
+
for cat, members in DEV_CATEGORIES.items():
|
|
292
|
+
if filename in members:
|
|
293
|
+
return cat
|
|
294
|
+
return "Other"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def tools_cell(tools: list[str]) -> str:
|
|
298
|
+
"""Format tools list for a table cell."""
|
|
299
|
+
if not tools:
|
|
300
|
+
return "*(none)*"
|
|
301
|
+
return ", ".join(tools)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def tool_matrix_row(skill: Skill, all_tools: list[str]) -> str:
|
|
305
|
+
"""Generate a tool access matrix row."""
|
|
306
|
+
cells = []
|
|
307
|
+
for t in all_tools:
|
|
308
|
+
# Handle mcp__context7__* wildcard
|
|
309
|
+
if t in skill.tools:
|
|
310
|
+
cells.append("x")
|
|
311
|
+
else:
|
|
312
|
+
# Check for mcp prefix tools
|
|
313
|
+
has_mcp = any(tool.startswith("mcp__") for tool in skill.tools)
|
|
314
|
+
if t == "mcp" and has_mcp:
|
|
315
|
+
cells.append("x")
|
|
316
|
+
else:
|
|
317
|
+
cells.append("")
|
|
318
|
+
name = skill.command.replace("/pan:", "")
|
|
319
|
+
return f"| {name} | " + " | ".join(cells) + " |"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def generate_reference(shipped: list[Skill], dev: list[Skill], version: str) -> str:
|
|
323
|
+
"""Generate the reference summary document."""
|
|
324
|
+
lines = []
|
|
325
|
+
w = lines.append
|
|
326
|
+
|
|
327
|
+
w("# PAN Wizard — Skills Reference")
|
|
328
|
+
w("")
|
|
329
|
+
w("Complete catalog of every skill (slash command) available in PAN Wizard, organized by purpose.")
|
|
330
|
+
w("Each entry shows the command, what it does, what tools it uses, and when to reach for it.")
|
|
331
|
+
w("")
|
|
332
|
+
w(f"**Version:** {version} | **Total:** {len(shipped)} shipped skills + {len(dev)} dev skills = {len(shipped) + len(dev)}")
|
|
333
|
+
w("")
|
|
334
|
+
w("> Auto-generated by `scripts/generate-skills-docs.py` — do not edit manually.")
|
|
335
|
+
w("")
|
|
336
|
+
w("---")
|
|
337
|
+
w("")
|
|
338
|
+
|
|
339
|
+
# ── Table of Contents ──
|
|
340
|
+
grouped = group_skills_by(shipped)
|
|
341
|
+
ordered_groups = [g for g in GROUP_ORDER if g in grouped]
|
|
342
|
+
# Add any groups not in GROUP_ORDER
|
|
343
|
+
for g in grouped:
|
|
344
|
+
if g not in ordered_groups:
|
|
345
|
+
ordered_groups.append(g)
|
|
346
|
+
|
|
347
|
+
w("## Table of Contents")
|
|
348
|
+
w("")
|
|
349
|
+
w(f"- [Shipped Skills ({len(shipped)})](#shipped-skills-{len(shipped)}) — installed into host projects")
|
|
350
|
+
for g in ordered_groups:
|
|
351
|
+
anchor = g.lower().replace(" & ", "--").replace(" ", "-")
|
|
352
|
+
w(f" - [{g}](#{anchor})")
|
|
353
|
+
w(f"- [Dev Skills ({len(dev)})](#dev-skills-{len(dev)}) — PAN source repo only")
|
|
354
|
+
for cat in DEV_CATEGORIES:
|
|
355
|
+
anchor = cat.lower().replace(" & ", "--").replace(" ", "-")
|
|
356
|
+
w(f" - [{cat}](#{anchor})")
|
|
357
|
+
w("")
|
|
358
|
+
w("---")
|
|
359
|
+
w("")
|
|
360
|
+
|
|
361
|
+
# ── Shipped Skills ──
|
|
362
|
+
w(f"## Shipped Skills ({len(shipped)})")
|
|
363
|
+
w("")
|
|
364
|
+
w("These are installed into host projects via the PAN installer and available to end users.")
|
|
365
|
+
w("")
|
|
366
|
+
|
|
367
|
+
for group_name in ordered_groups:
|
|
368
|
+
group_skills = grouped[group_name]
|
|
369
|
+
w(f"### {group_name}")
|
|
370
|
+
w("")
|
|
371
|
+
|
|
372
|
+
# Summary table
|
|
373
|
+
w("| Skill | Tools | Description |")
|
|
374
|
+
w("|-------|-------|-------------|")
|
|
375
|
+
for s in group_skills:
|
|
376
|
+
w(f"| `{s.command}` | {tools_cell(s.tools)} | {s.description} |")
|
|
377
|
+
w("")
|
|
378
|
+
|
|
379
|
+
# Individual entries
|
|
380
|
+
for s in group_skills:
|
|
381
|
+
w(f"#### {s.command}")
|
|
382
|
+
w("")
|
|
383
|
+
if s.description:
|
|
384
|
+
w(s.description)
|
|
385
|
+
w("")
|
|
386
|
+
if s.argument_hint:
|
|
387
|
+
w(f"```")
|
|
388
|
+
w(f"{s.command} {s.argument_hint}")
|
|
389
|
+
w(f"```")
|
|
390
|
+
else:
|
|
391
|
+
w(f"```")
|
|
392
|
+
w(f"{s.command}")
|
|
393
|
+
w(f"```")
|
|
394
|
+
w("")
|
|
395
|
+
w(f"**Tools:** {tools_cell(s.tools)} ")
|
|
396
|
+
w(f"**Group:** {s.group} ")
|
|
397
|
+
w(f"**Lines:** {s.line_count}")
|
|
398
|
+
w("")
|
|
399
|
+
|
|
400
|
+
w("---")
|
|
401
|
+
w("")
|
|
402
|
+
|
|
403
|
+
# ── Dev Skills ──
|
|
404
|
+
w(f"## Dev Skills ({len(dev)})")
|
|
405
|
+
w("")
|
|
406
|
+
w("These exist only in the PAN source repository (`.claude/commands/`) and are NOT shipped to end users.")
|
|
407
|
+
w("")
|
|
408
|
+
|
|
409
|
+
dev_by_cat: dict[str, list[Skill]] = {}
|
|
410
|
+
for s in dev:
|
|
411
|
+
cat = dev_category_for(s.filename)
|
|
412
|
+
dev_by_cat.setdefault(cat, []).append(s)
|
|
413
|
+
|
|
414
|
+
for cat_name in DEV_CATEGORIES:
|
|
415
|
+
if cat_name not in dev_by_cat:
|
|
416
|
+
continue
|
|
417
|
+
cat_skills = dev_by_cat[cat_name]
|
|
418
|
+
w(f"### {cat_name}")
|
|
419
|
+
w("")
|
|
420
|
+
w("| Skill | Description |")
|
|
421
|
+
w("|-------|-------------|")
|
|
422
|
+
for s in cat_skills:
|
|
423
|
+
desc = s.description or s.heading or s.filename
|
|
424
|
+
w(f"| `{s.command}` | {desc} |")
|
|
425
|
+
w("")
|
|
426
|
+
|
|
427
|
+
for s in cat_skills:
|
|
428
|
+
w(f"#### {s.command}")
|
|
429
|
+
w("")
|
|
430
|
+
desc = s.description or s.heading or ""
|
|
431
|
+
if desc:
|
|
432
|
+
w(desc)
|
|
433
|
+
w("")
|
|
434
|
+
w(f"**Lines:** {s.line_count}")
|
|
435
|
+
w("")
|
|
436
|
+
|
|
437
|
+
w("---")
|
|
438
|
+
w("")
|
|
439
|
+
|
|
440
|
+
# Handle any dev skills not in a named category
|
|
441
|
+
other_skills = dev_by_cat.get("Other", [])
|
|
442
|
+
if other_skills:
|
|
443
|
+
w("### Other")
|
|
444
|
+
w("")
|
|
445
|
+
w("| Skill | Description |")
|
|
446
|
+
w("|-------|-------------|")
|
|
447
|
+
for s in other_skills:
|
|
448
|
+
desc = s.description or s.heading or s.filename
|
|
449
|
+
w(f"| `{s.command}` | {desc} |")
|
|
450
|
+
w("")
|
|
451
|
+
for s in other_skills:
|
|
452
|
+
w(f"#### {s.command}")
|
|
453
|
+
w("")
|
|
454
|
+
if s.description:
|
|
455
|
+
w(s.description)
|
|
456
|
+
w("")
|
|
457
|
+
w(f"**Lines:** {s.line_count}")
|
|
458
|
+
w("")
|
|
459
|
+
w("---")
|
|
460
|
+
w("")
|
|
461
|
+
|
|
462
|
+
# ── Tool Access Matrix ──
|
|
463
|
+
w("## Tool Access Matrix")
|
|
464
|
+
w("")
|
|
465
|
+
w("Which tools each shipped skill can use:")
|
|
466
|
+
w("")
|
|
467
|
+
|
|
468
|
+
# Header
|
|
469
|
+
tool_short = ["Read", "Write", "Edit", "Bash", "Grep", "Glob",
|
|
470
|
+
"Agent", "Task", "TodoWrite", "AskUser",
|
|
471
|
+
"SlashCmd", "WebSearch", "WebFetch", "mcp"]
|
|
472
|
+
tool_full = ["Read", "Write", "Edit", "Bash", "Grep", "Glob",
|
|
473
|
+
"Agent", "Task", "TodoWrite", "AskUserQuestion",
|
|
474
|
+
"SlashCommand", "WebSearch", "WebFetch"]
|
|
475
|
+
header = "| Skill | " + " | ".join(tool_short) + " |"
|
|
476
|
+
sep = "|-------|" + "|".join([":---:" for _ in tool_short]) + "|"
|
|
477
|
+
w(header)
|
|
478
|
+
w(sep)
|
|
479
|
+
|
|
480
|
+
for s in shipped:
|
|
481
|
+
cells = []
|
|
482
|
+
for short, full in zip(tool_short, tool_full + ["mcp"]):
|
|
483
|
+
if full == "mcp":
|
|
484
|
+
has_mcp = any(t.startswith("mcp__") for t in s.tools)
|
|
485
|
+
cells.append("x" if has_mcp else "")
|
|
486
|
+
elif full in s.tools:
|
|
487
|
+
cells.append("x")
|
|
488
|
+
else:
|
|
489
|
+
cells.append("")
|
|
490
|
+
name = s.command.replace("/pan:", "")
|
|
491
|
+
w(f"| {name} | " + " | ".join(cells) + " |")
|
|
492
|
+
|
|
493
|
+
w("")
|
|
494
|
+
|
|
495
|
+
# ── Quick Reference ──
|
|
496
|
+
w("---")
|
|
497
|
+
w("")
|
|
498
|
+
w("## Quick Reference — Common Workflows")
|
|
499
|
+
w("")
|
|
500
|
+
w("| Scenario | Skill sequence |")
|
|
501
|
+
w("|----------|---------------|")
|
|
502
|
+
w("| Greenfield project | `new-project` > `discuss-phase` > `plan-phase` > `exec-phase` > `verify-phase` |")
|
|
503
|
+
w("| Brownfield project | `map-codebase` > `new-project` > `discuss-phase` > `plan-phase` > `exec-phase` > `verify-phase` |")
|
|
504
|
+
w("| Quick bug fix | `quick` |")
|
|
505
|
+
w("| Substantial ad-hoc work | `quick --full` |")
|
|
506
|
+
w("| Start of day | `progress` > `resume` |")
|
|
507
|
+
w("| End of day | `pause` |")
|
|
508
|
+
w("| New version cycle | `milestone-done` > `milestone-new` |")
|
|
509
|
+
w("| Automated from PRD | `new-project --auto @prd.md` > `plan-phase --auto` > `exec-phase` > `verify-phase` |")
|
|
510
|
+
w("| Continuous improvement | `focus-auto --category <cat>` |")
|
|
511
|
+
w("| Execute micro-prompts | `focus-auto --category prompts` |")
|
|
512
|
+
w("| Feature investigation | `focus-design \"description\"` |")
|
|
513
|
+
w("| Doc hygiene | `focus-drift-walking` or `focus-doc-audit` |")
|
|
514
|
+
w("")
|
|
515
|
+
|
|
516
|
+
return "\n".join(lines)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
# ---------------------------------------------------------------------------
|
|
520
|
+
# Main
|
|
521
|
+
# ---------------------------------------------------------------------------
|
|
522
|
+
|
|
523
|
+
def main():
|
|
524
|
+
dry_run = "--dry-run" in sys.argv
|
|
525
|
+
full_only = "--full-only" in sys.argv
|
|
526
|
+
ref_only = "--ref-only" in sys.argv
|
|
527
|
+
|
|
528
|
+
version = get_version()
|
|
529
|
+
shipped = load_shipped_skills()
|
|
530
|
+
dev = load_dev_skills()
|
|
531
|
+
|
|
532
|
+
print(f"PAN Wizard v{version}")
|
|
533
|
+
print(f"Shipped skills: {len(shipped)} (from {SHIPPED_DIR})")
|
|
534
|
+
print(f"Dev skills: {len(dev)} (from {DEV_DIR})")
|
|
535
|
+
print()
|
|
536
|
+
|
|
537
|
+
if not ref_only:
|
|
538
|
+
full_text = generate_full_text(shipped, dev, version)
|
|
539
|
+
full_lines = full_text.count("\n") + 1
|
|
540
|
+
if dry_run:
|
|
541
|
+
print(f"[dry-run] Would write {OUT_FULL_TEXT} ({full_lines} lines)")
|
|
542
|
+
else:
|
|
543
|
+
OUT_FULL_TEXT.write_text(full_text, encoding="utf-8")
|
|
544
|
+
print(f"Wrote {OUT_FULL_TEXT} ({full_lines} lines)")
|
|
545
|
+
|
|
546
|
+
if not full_only:
|
|
547
|
+
reference = generate_reference(shipped, dev, version)
|
|
548
|
+
ref_lines = reference.count("\n") + 1
|
|
549
|
+
if dry_run:
|
|
550
|
+
print(f"[dry-run] Would write {OUT_REFERENCE} ({ref_lines} lines)")
|
|
551
|
+
else:
|
|
552
|
+
OUT_REFERENCE.write_text(reference, encoding="utf-8")
|
|
553
|
+
print(f"Wrote {OUT_REFERENCE} ({ref_lines} lines)")
|
|
554
|
+
|
|
555
|
+
print()
|
|
556
|
+
print("Done.")
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
if __name__ == "__main__":
|
|
560
|
+
main()
|