pan-wizard 2.8.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.
Files changed (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -0
  3. package/agents/pan-debugger.md +1246 -0
  4. package/agents/pan-document_code.md +965 -0
  5. package/agents/pan-executor.md +469 -0
  6. package/agents/pan-integration-checker.md +443 -0
  7. package/agents/pan-phase-researcher.md +572 -0
  8. package/agents/pan-plan-checker.md +763 -0
  9. package/agents/pan-planner.md +1297 -0
  10. package/agents/pan-project-researcher.md +647 -0
  11. package/agents/pan-research-synthesizer.md +239 -0
  12. package/agents/pan-reviewer.md +112 -0
  13. package/agents/pan-roadmapper.md +642 -0
  14. package/agents/pan-verifier.md +672 -0
  15. package/assets/pan-logo-2000-transparent.svg +30 -0
  16. package/assets/pan-logo-2000.svg +43 -0
  17. package/assets/terminal.svg +119 -0
  18. package/bin/install-lib.cjs +616 -0
  19. package/bin/install.js +1936 -0
  20. package/commands/pan/add-phase.md +44 -0
  21. package/commands/pan/assumptions.md +47 -0
  22. package/commands/pan/audit-deployment.md +378 -0
  23. package/commands/pan/debug.md +168 -0
  24. package/commands/pan/discord.md +19 -0
  25. package/commands/pan/discuss-phase.md +84 -0
  26. package/commands/pan/exec-phase.md +45 -0
  27. package/commands/pan/focus-auto.md +323 -0
  28. package/commands/pan/focus-design.md +816 -0
  29. package/commands/pan/focus-exec.md +316 -0
  30. package/commands/pan/focus-plan.md +101 -0
  31. package/commands/pan/focus-scan.md +272 -0
  32. package/commands/pan/focus-sync.md +104 -0
  33. package/commands/pan/health.md +23 -0
  34. package/commands/pan/help.md +23 -0
  35. package/commands/pan/insert-phase.md +33 -0
  36. package/commands/pan/map-codebase.md +72 -0
  37. package/commands/pan/milestone-audit.md +37 -0
  38. package/commands/pan/milestone-cleanup.md +19 -0
  39. package/commands/pan/milestone-done.md +137 -0
  40. package/commands/pan/milestone-gaps.md +35 -0
  41. package/commands/pan/milestone-new.md +45 -0
  42. package/commands/pan/new-project.md +43 -0
  43. package/commands/pan/patches.md +110 -0
  44. package/commands/pan/pause.md +39 -0
  45. package/commands/pan/phase-budget.md +23 -0
  46. package/commands/pan/phase-tests.md +42 -0
  47. package/commands/pan/plan-phase.md +46 -0
  48. package/commands/pan/profile.md +36 -0
  49. package/commands/pan/progress.md +25 -0
  50. package/commands/pan/quick.md +42 -0
  51. package/commands/pan/remove-phase.md +32 -0
  52. package/commands/pan/research-phase.md +190 -0
  53. package/commands/pan/resume.md +41 -0
  54. package/commands/pan/retro.md +33 -0
  55. package/commands/pan/settings.md +37 -0
  56. package/commands/pan/todo-add.md +48 -0
  57. package/commands/pan/todo-check.md +46 -0
  58. package/commands/pan/update.md +38 -0
  59. package/commands/pan/verify-phase.md +39 -0
  60. package/hooks/dist/pan-check-update.js +62 -0
  61. package/hooks/dist/pan-context-monitor.js +122 -0
  62. package/hooks/dist/pan-statusline.js +108 -0
  63. package/package.json +66 -0
  64. package/pan-wizard-core/bin/lib/codebase.cjs +746 -0
  65. package/pan-wizard-core/bin/lib/commands.cjs +1435 -0
  66. package/pan-wizard-core/bin/lib/config.cjs +611 -0
  67. package/pan-wizard-core/bin/lib/constants.cjs +696 -0
  68. package/pan-wizard-core/bin/lib/context-budget.cjs +150 -0
  69. package/pan-wizard-core/bin/lib/core.cjs +650 -0
  70. package/pan-wizard-core/bin/lib/focus.cjs +900 -0
  71. package/pan-wizard-core/bin/lib/frontmatter.cjs +442 -0
  72. package/pan-wizard-core/bin/lib/init.cjs +881 -0
  73. package/pan-wizard-core/bin/lib/milestone.cjs +276 -0
  74. package/pan-wizard-core/bin/lib/phase.cjs +1212 -0
  75. package/pan-wizard-core/bin/lib/roadmap.cjs +470 -0
  76. package/pan-wizard-core/bin/lib/state.cjs +1029 -0
  77. package/pan-wizard-core/bin/lib/template.cjs +314 -0
  78. package/pan-wizard-core/bin/lib/utils.cjs +171 -0
  79. package/pan-wizard-core/bin/lib/verify.cjs +1808 -0
  80. package/pan-wizard-core/bin/pan-tools.cjs +773 -0
  81. package/pan-wizard-core/references/checkpoints.md +776 -0
  82. package/pan-wizard-core/references/continuation-format.md +249 -0
  83. package/pan-wizard-core/references/decimal-phase-calculation.md +65 -0
  84. package/pan-wizard-core/references/git-integration.md +248 -0
  85. package/pan-wizard-core/references/git-planning-commit.md +38 -0
  86. package/pan-wizard-core/references/model-profile-resolution.md +34 -0
  87. package/pan-wizard-core/references/model-profiles.md +111 -0
  88. package/pan-wizard-core/references/phase-argument-parsing.md +61 -0
  89. package/pan-wizard-core/references/planning-config.md +196 -0
  90. package/pan-wizard-core/references/questioning.md +145 -0
  91. package/pan-wizard-core/references/tdd.md +263 -0
  92. package/pan-wizard-core/references/ui-brand.md +160 -0
  93. package/pan-wizard-core/references/verification-patterns.md +612 -0
  94. package/pan-wizard-core/templates/codebase/architecture.md +283 -0
  95. package/pan-wizard-core/templates/codebase/best-practices.md +133 -0
  96. package/pan-wizard-core/templates/codebase/concerns.md +325 -0
  97. package/pan-wizard-core/templates/codebase/conventions.md +307 -0
  98. package/pan-wizard-core/templates/codebase/integrations.md +305 -0
  99. package/pan-wizard-core/templates/codebase/relationships.md +124 -0
  100. package/pan-wizard-core/templates/codebase/stack.md +199 -0
  101. package/pan-wizard-core/templates/codebase/structure.md +298 -0
  102. package/pan-wizard-core/templates/codebase/testing.md +480 -0
  103. package/pan-wizard-core/templates/config.json +37 -0
  104. package/pan-wizard-core/templates/context.md +283 -0
  105. package/pan-wizard-core/templates/continue-here.md +78 -0
  106. package/pan-wizard-core/templates/debug-subagent-prompt.md +91 -0
  107. package/pan-wizard-core/templates/debug.md +164 -0
  108. package/pan-wizard-core/templates/discovery.md +146 -0
  109. package/pan-wizard-core/templates/milestone-archive.md +123 -0
  110. package/pan-wizard-core/templates/milestone.md +115 -0
  111. package/pan-wizard-core/templates/phase-prompt.md +593 -0
  112. package/pan-wizard-core/templates/planner-subagent-prompt.md +117 -0
  113. package/pan-wizard-core/templates/project.md +184 -0
  114. package/pan-wizard-core/templates/requirements.md +231 -0
  115. package/pan-wizard-core/templates/research-project/architecture.md +204 -0
  116. package/pan-wizard-core/templates/research-project/features.md +147 -0
  117. package/pan-wizard-core/templates/research-project/pitfalls.md +200 -0
  118. package/pan-wizard-core/templates/research-project/stack.md +120 -0
  119. package/pan-wizard-core/templates/research-project/summary.md +170 -0
  120. package/pan-wizard-core/templates/research.md +552 -0
  121. package/pan-wizard-core/templates/retrospective.md +54 -0
  122. package/pan-wizard-core/templates/roadmap.md +202 -0
  123. package/pan-wizard-core/templates/standards.md +24 -0
  124. package/pan-wizard-core/templates/state.md +176 -0
  125. package/pan-wizard-core/templates/summary-complex.md +59 -0
  126. package/pan-wizard-core/templates/summary-minimal.md +41 -0
  127. package/pan-wizard-core/templates/summary-standard.md +49 -0
  128. package/pan-wizard-core/templates/summary.md +249 -0
  129. package/pan-wizard-core/templates/uat.md +247 -0
  130. package/pan-wizard-core/templates/user-setup.md +311 -0
  131. package/pan-wizard-core/templates/validation.md +76 -0
  132. package/pan-wizard-core/templates/verification-report.md +322 -0
  133. package/pan-wizard-core/workflows/add-phase.md +111 -0
  134. package/pan-wizard-core/workflows/assumptions.md +178 -0
  135. package/pan-wizard-core/workflows/diagnose-issues.md +219 -0
  136. package/pan-wizard-core/workflows/discuss-phase.md +542 -0
  137. package/pan-wizard-core/workflows/exec-phase.md +572 -0
  138. package/pan-wizard-core/workflows/execute-plan.md +448 -0
  139. package/pan-wizard-core/workflows/health.md +156 -0
  140. package/pan-wizard-core/workflows/help.md +431 -0
  141. package/pan-wizard-core/workflows/insert-phase.md +129 -0
  142. package/pan-wizard-core/workflows/map-codebase.md +401 -0
  143. package/pan-wizard-core/workflows/milestone-audit.md +297 -0
  144. package/pan-wizard-core/workflows/milestone-cleanup.md +152 -0
  145. package/pan-wizard-core/workflows/milestone-gaps.md +274 -0
  146. package/pan-wizard-core/workflows/milestone-new.md +382 -0
  147. package/pan-wizard-core/workflows/new-project.md +1178 -0
  148. package/pan-wizard-core/workflows/pause.md +122 -0
  149. package/pan-wizard-core/workflows/phase-tests.md +388 -0
  150. package/pan-wizard-core/workflows/plan-phase.md +569 -0
  151. package/pan-wizard-core/workflows/profile.md +115 -0
  152. package/pan-wizard-core/workflows/progress.md +381 -0
  153. package/pan-wizard-core/workflows/quick.md +453 -0
  154. package/pan-wizard-core/workflows/remove-phase.md +154 -0
  155. package/pan-wizard-core/workflows/research-phase.md +73 -0
  156. package/pan-wizard-core/workflows/resume-project.md +306 -0
  157. package/pan-wizard-core/workflows/retro.md +121 -0
  158. package/pan-wizard-core/workflows/settings.md +213 -0
  159. package/pan-wizard-core/workflows/todo-add.md +157 -0
  160. package/pan-wizard-core/workflows/todo-check.md +176 -0
  161. package/pan-wizard-core/workflows/transition.md +544 -0
  162. package/pan-wizard-core/workflows/update.md +219 -0
  163. package/pan-wizard-core/workflows/verify-phase.md +301 -0
  164. package/scripts/build-hooks.js +43 -0
@@ -0,0 +1,616 @@
1
+ /**
2
+ * PAN Wizard Installer — Extracted Pure Functions
3
+ *
4
+ * These functions were extracted from bin/install.js to enable independent testing.
5
+ * install.js requires this module and uses these functions directly.
6
+ *
7
+ * All functions in this file are PURE (no fs, no process.env, no side effects)
8
+ * unless explicitly documented otherwise.
9
+ */
10
+
11
+ const path = require('path');
12
+ const os = require('os');
13
+
14
+ // ─── Constants / Lookup Tables ──────────────────────────────────────────────
15
+
16
+ /** Color name → hex mapping for OpenCode compatibility */
17
+ const colorNameToHex = {
18
+ cyan: '#00FFFF',
19
+ red: '#FF0000',
20
+ green: '#00FF00',
21
+ blue: '#0000FF',
22
+ yellow: '#FFFF00',
23
+ magenta: '#FF00FF',
24
+ orange: '#FFA500',
25
+ purple: '#800080',
26
+ pink: '#FFC0CB',
27
+ white: '#FFFFFF',
28
+ black: '#000000',
29
+ gray: '#808080',
30
+ grey: '#808080',
31
+ };
32
+
33
+ /** Claude → OpenCode tool name mapping */
34
+ const claudeToOpencodeTools = {
35
+ AskUserQuestion: 'question',
36
+ SlashCommand: 'skill',
37
+ TodoWrite: 'todowrite',
38
+ WebFetch: 'webfetch',
39
+ WebSearch: 'websearch',
40
+ };
41
+
42
+ /** Claude → Gemini CLI tool name mapping (snake_case) */
43
+ const claudeToGeminiTools = {
44
+ Read: 'read_file',
45
+ Write: 'write_file',
46
+ Edit: 'replace',
47
+ Bash: 'run_shell_command',
48
+ Glob: 'glob',
49
+ Grep: 'search_file_content',
50
+ WebSearch: 'google_web_search',
51
+ WebFetch: 'web_fetch',
52
+ TodoWrite: 'write_todos',
53
+ AskUserQuestion: 'ask_user',
54
+ };
55
+
56
+ /** Claude → Copilot CLI tool name mapping */
57
+ const claudeToCopilotTools = {
58
+ Read: 'read',
59
+ Write: 'edit',
60
+ Edit: 'edit',
61
+ Bash: 'bash',
62
+ Glob: 'glob',
63
+ Grep: 'search',
64
+ WebSearch: 'web',
65
+ WebFetch: 'web',
66
+ TodoWrite: 'todo',
67
+ AskUserQuestion: null,
68
+ Agent: 'agent',
69
+ Task: 'agent',
70
+ };
71
+
72
+ // ─── Core Utility Functions ─────────────────────────────────────────────────
73
+
74
+ /** Map runtime name → config directory name */
75
+ function getDirName(runtime) {
76
+ if (runtime === 'opencode') return '.opencode';
77
+ if (runtime === 'gemini') return '.gemini';
78
+ if (runtime === 'codex') return '.codex';
79
+ if (runtime === 'copilot') return '.github';
80
+ return '.claude';
81
+ }
82
+
83
+ /**
84
+ * Get config dir path relative to home for hook templating.
85
+ * Returns quoted path segments for path.join() insertion.
86
+ */
87
+ function getConfigDirFromHome(runtime, isGlobal) {
88
+ if (!isGlobal) {
89
+ return `'${getDirName(runtime)}'`;
90
+ }
91
+ if (runtime === 'opencode') return "'.config', 'opencode'";
92
+ if (runtime === 'gemini') return "'.gemini'";
93
+ if (runtime === 'codex') return "'.codex'";
94
+ if (runtime === 'copilot') return "'.copilot'";
95
+ return "'.claude'";
96
+ }
97
+
98
+ /** Expand ~ to home directory */
99
+ function expandTilde(filePath) {
100
+ if (filePath && filePath.startsWith('~/')) {
101
+ return path.join(os.homedir(), filePath.slice(2));
102
+ }
103
+ return filePath;
104
+ }
105
+
106
+ /** Collapse whitespace to single line */
107
+ function toSingleLine(value) {
108
+ return value.replace(/\s+/g, ' ').trim();
109
+ }
110
+
111
+ /** Wrap value in JSON.stringify for YAML safety */
112
+ function yamlQuote(value) {
113
+ return JSON.stringify(value);
114
+ }
115
+
116
+ // ─── Frontmatter Extraction ─────────────────────────────────────────────────
117
+
118
+ /** Split markdown into { frontmatter, body } */
119
+ function extractFrontmatterAndBody(content) {
120
+ if (!content.startsWith('---')) {
121
+ return { frontmatter: null, body: content };
122
+ }
123
+ const endIndex = content.indexOf('---', 3);
124
+ if (endIndex === -1) {
125
+ return { frontmatter: null, body: content };
126
+ }
127
+ return {
128
+ frontmatter: content.substring(3, endIndex).trim(),
129
+ body: content.substring(endIndex + 3),
130
+ };
131
+ }
132
+
133
+ /** Extract a field value from YAML frontmatter string */
134
+ function extractFrontmatterField(frontmatter, fieldName) {
135
+ const regex = new RegExp(`^${fieldName}:\\s*(.+)$`, 'm');
136
+ const match = frontmatter.match(regex);
137
+ if (!match) return null;
138
+ return match[1].trim().replace(/^['"]|['"]$/g, '');
139
+ }
140
+
141
+ // ─── Tool Name Converters ───────────────────────────────────────────────────
142
+
143
+ /** Convert Claude tool name → OpenCode format */
144
+ function convertToolName(claudeTool) {
145
+ if (claudeToOpencodeTools[claudeTool]) {
146
+ return claudeToOpencodeTools[claudeTool];
147
+ }
148
+ if (claudeTool.startsWith('mcp__')) {
149
+ return claudeTool;
150
+ }
151
+ return claudeTool.toLowerCase();
152
+ }
153
+
154
+ /**
155
+ * Convert Claude tool name → Gemini CLI format.
156
+ * Returns null for tools that should be excluded (MCP, Task).
157
+ */
158
+ function convertGeminiToolName(claudeTool) {
159
+ if (claudeTool.startsWith('mcp__')) return null;
160
+ if (claudeTool === 'Task') return null;
161
+ if (claudeToGeminiTools[claudeTool]) {
162
+ return claudeToGeminiTools[claudeTool];
163
+ }
164
+ return claudeTool.toLowerCase();
165
+ }
166
+
167
+ /**
168
+ * Convert Claude tool name → Copilot CLI format.
169
+ * Returns null for tools that should be excluded (AskUserQuestion).
170
+ */
171
+ function convertCopilotToolName(claudeTool) {
172
+ if (claudeTool.startsWith('mcp__')) return claudeTool;
173
+ if (claudeTool in claudeToCopilotTools) {
174
+ return claudeToCopilotTools[claudeTool];
175
+ }
176
+ return claudeTool.toLowerCase();
177
+ }
178
+
179
+ // ─── Slash Command Converters ───────────────────────────────────────────────
180
+
181
+ /** /pan:command → $pan-command (Codex format) */
182
+ function convertSlashCommandsToCodexSkillMentions(content) {
183
+ let converted = content.replace(/\/pan:([a-z0-9-]+)/gi, (_, commandName) => {
184
+ return `$pan-${String(commandName).toLowerCase()}`;
185
+ });
186
+ converted = converted.replace(/\/pan-help\b/g, '$pan-help');
187
+ return converted;
188
+ }
189
+
190
+ /** Claude markdown → Codex markdown (slash commands + $ARGUMENTS) */
191
+ function convertClaudeToCodexMarkdown(content) {
192
+ let converted = convertSlashCommandsToCodexSkillMentions(content);
193
+ converted = converted.replace(/\$ARGUMENTS\b/g, '{{PAN_ARGS}}');
194
+ return converted;
195
+ }
196
+
197
+ /** /pan:command and $pan-command → /pan-command (Copilot format) */
198
+ function convertSlashCommandsToCopilotSkillMentions(content) {
199
+ let converted = content.replace(/\/pan:([a-z0-9-]+)/gi, (_, commandName) => {
200
+ return `/pan-${String(commandName).toLowerCase()}`;
201
+ });
202
+ converted = converted.replace(/\$pan-([a-z0-9-]+)/gi, (_, commandName) => {
203
+ return `/pan-${String(commandName).toLowerCase()}`;
204
+ });
205
+ return converted;
206
+ }
207
+
208
+ // ─── Content Converters ─────────────────────────────────────────────────────
209
+
210
+ /** Rewrite AskUserQuestion blocks for Copilot CLI (numbered menus) */
211
+ function rewriteAskUserQuestionForCopilot(content) {
212
+ let result = content;
213
+ const blockRe = /(?:Use\s+)?AskUserQuestion(?:\s*\(multiSelect:\s*true\))?:\s*\n-\s*header:\s*"[^"]*"\s*\n-\s*question:\s*"([^"]*)"\s*\n-\s*options:\s*\n((?:\s+-\s+"[^"]*"(?:\s*—[^\n]*)?\n?)+)/g;
214
+
215
+ result = result.replace(blockRe, (match, question, optionsBlock) => {
216
+ const isMultiSelect = match.includes('multiSelect');
217
+ const optionLines = optionsBlock.match(/^\s+-\s+"([^"]*)"(?:\s*—\s*(.*))?$/gm) || [];
218
+ const numbered = optionLines.map((line, i) => {
219
+ const optMatch = line.match(/^\s+-\s+"([^"]*)"(?:\s*—\s*(.*))?$/);
220
+ if (!optMatch) return `${i + 1}. ${line.trim()}`;
221
+ const label = optMatch[1];
222
+ const desc = optMatch[2] ? optMatch[2].trim() : '';
223
+ return desc ? `${i + 1}. **${label}** — ${desc}` : `${i + 1}. **${label}**`;
224
+ });
225
+ const selectInstruction = isMultiSelect
226
+ ? 'Type the numbers you want, separated by commas (e.g., 1,3). Or type your own answer.'
227
+ : 'Type a number or label to choose. Or type your own answer.';
228
+ const prefix = isMultiSelect ? 'Ask the user (select one or more)' : 'Ask the user';
229
+ return `${prefix}:\n\n**${question}**\n\n${numbered.join('\n')}\n\n${selectInstruction}\nWait for the user's response before continuing.\n`;
230
+ });
231
+
232
+ result = result.replace(/\bUse AskUserQuestion\b(?!\s*[:(])/g, 'Ask the user with numbered options');
233
+ result = result.replace(/\buse AskUserQuestion\b(?!\s*[:(])/g, 'ask the user with numbered options');
234
+ result = result.replace(/\bAskUserQuestion\b/g, 'ask_user');
235
+ return result;
236
+ }
237
+
238
+ /** Claude markdown → Copilot markdown (slash commands + Task + AskUserQuestion) */
239
+ function convertClaudeToCopilotMarkdown(content) {
240
+ let converted = convertSlashCommandsToCopilotSkillMentions(content);
241
+ converted = converted.replace(/Task\s*\(\s*subagent_type\s*=\s*["']([^"']+)["']\s*\)/g, (_, agentName) => {
242
+ return `/agent ${agentName}`;
243
+ });
244
+ converted = converted.replace(/Agent\s*\(\s*subagent_type\s*=\s*["']([^"']+)["']\s*\)/g, (_, agentName) => {
245
+ return `/agent ${agentName}`;
246
+ });
247
+ converted = rewriteAskUserQuestionForCopilot(converted);
248
+ return converted;
249
+ }
250
+
251
+ /** Strip <sub>text</sub> → *(text)* for terminal output */
252
+ function stripSubTags(content) {
253
+ return content.replace(/<sub>(.*?)<\/sub>/g, '*($1)*');
254
+ }
255
+
256
+ /** Claude agent → Gemini agent (frontmatter + tool conversion + ${VAR} escaping) */
257
+ function convertClaudeToGeminiAgent(content) {
258
+ if (!content.startsWith('---')) return content;
259
+ const endIndex = content.indexOf('---', 3);
260
+ if (endIndex === -1) return content;
261
+
262
+ const frontmatter = content.substring(3, endIndex).trim();
263
+ const body = content.substring(endIndex + 3);
264
+ const lines = frontmatter.split('\n');
265
+ const newLines = [];
266
+ let inAllowedTools = false;
267
+ const tools = [];
268
+
269
+ for (const line of lines) {
270
+ const trimmed = line.trim();
271
+ if (trimmed.startsWith('allowed-tools:')) { inAllowedTools = true; continue; }
272
+ if (trimmed.startsWith('tools:')) {
273
+ const toolsValue = trimmed.substring(6).trim();
274
+ if (toolsValue) {
275
+ const parsed = toolsValue.split(',').map(t => t.trim()).filter(t => t);
276
+ for (const t of parsed) {
277
+ const mapped = convertGeminiToolName(t);
278
+ if (mapped) tools.push(mapped);
279
+ }
280
+ } else {
281
+ inAllowedTools = true;
282
+ }
283
+ continue;
284
+ }
285
+ if (trimmed.startsWith('color:')) continue;
286
+ if (inAllowedTools) {
287
+ if (trimmed.startsWith('- ')) {
288
+ const mapped = convertGeminiToolName(trimmed.substring(2).trim());
289
+ if (mapped) tools.push(mapped);
290
+ continue;
291
+ } else if (trimmed && !trimmed.startsWith('-')) {
292
+ inAllowedTools = false;
293
+ }
294
+ }
295
+ if (!inAllowedTools) newLines.push(line);
296
+ }
297
+
298
+ if (tools.length > 0) {
299
+ newLines.push('tools:');
300
+ for (const tool of tools) newLines.push(` - ${tool}`);
301
+ }
302
+
303
+ const newFrontmatter = newLines.join('\n').trim();
304
+ const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1');
305
+ return `---\n${newFrontmatter}\n---${stripSubTags(escapedBody)}`;
306
+ }
307
+
308
+ /** Claude frontmatter → OpenCode frontmatter (tool names + paths + colors) */
309
+ function convertClaudeToOpencodeFrontmatter(content) {
310
+ let convertedContent = content;
311
+ convertedContent = convertedContent.replace(/\bAskUserQuestion\b/g, 'question');
312
+ convertedContent = convertedContent.replace(/\bSlashCommand\b/g, 'skill');
313
+ convertedContent = convertedContent.replace(/\bTodoWrite\b/g, 'todowrite');
314
+ convertedContent = convertedContent.replace(/\/pan:/g, '/pan-');
315
+ convertedContent = convertedContent.replace(/~\/\.claude\b/g, '~/.config/opencode');
316
+ convertedContent = convertedContent.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
317
+
318
+ if (!convertedContent.startsWith('---')) return convertedContent;
319
+ const endIndex = convertedContent.indexOf('---', 3);
320
+ if (endIndex === -1) return convertedContent;
321
+
322
+ const frontmatter = convertedContent.substring(3, endIndex).trim();
323
+ const body = convertedContent.substring(endIndex + 3);
324
+ const lines = frontmatter.split('\n');
325
+ const newLines = [];
326
+ let inAllowedTools = false;
327
+ const allowedTools = [];
328
+
329
+ for (const line of lines) {
330
+ const trimmed = line.trim();
331
+ if (trimmed.startsWith('allowed-tools:')) { inAllowedTools = true; continue; }
332
+ if (trimmed.startsWith('tools:')) {
333
+ const toolsValue = trimmed.substring(6).trim();
334
+ if (toolsValue) {
335
+ const parsed = toolsValue.split(',').map(t => t.trim()).filter(t => t);
336
+ allowedTools.push(...parsed);
337
+ }
338
+ continue;
339
+ }
340
+ if (trimmed.startsWith('name:')) continue;
341
+ if (trimmed.startsWith('color:')) {
342
+ const colorValue = trimmed.substring(6).trim().toLowerCase();
343
+ const hexColor = colorNameToHex[colorValue];
344
+ if (hexColor) {
345
+ newLines.push(`color: "${hexColor}"`);
346
+ } else if (colorValue.startsWith('#') && /^#[0-9a-f]{3}$|^#[0-9a-f]{6}$/i.test(colorValue)) {
347
+ newLines.push(line);
348
+ }
349
+ continue;
350
+ }
351
+ if (inAllowedTools) {
352
+ if (trimmed.startsWith('- ')) {
353
+ allowedTools.push(trimmed.substring(2).trim());
354
+ continue;
355
+ } else if (trimmed && !trimmed.startsWith('-')) {
356
+ inAllowedTools = false;
357
+ }
358
+ }
359
+ if (!inAllowedTools) newLines.push(line);
360
+ }
361
+
362
+ if (allowedTools.length > 0) {
363
+ newLines.push('tools:');
364
+ for (const tool of allowedTools) {
365
+ newLines.push(` ${convertToolName(tool)}: true`);
366
+ }
367
+ }
368
+
369
+ const newFrontmatter = newLines.join('\n').trim();
370
+ return `---\n${newFrontmatter}\n---${body}`;
371
+ }
372
+
373
+ /** Claude markdown → Gemini TOML */
374
+ function convertClaudeToGeminiToml(content) {
375
+ if (!content.startsWith('---')) {
376
+ return `prompt = ${JSON.stringify(content)}\n`;
377
+ }
378
+ const endIndex = content.indexOf('---', 3);
379
+ if (endIndex === -1) {
380
+ return `prompt = ${JSON.stringify(content)}\n`;
381
+ }
382
+
383
+ const frontmatter = content.substring(3, endIndex).trim();
384
+ const body = content.substring(endIndex + 3).trim();
385
+ let description = '';
386
+ const lines = frontmatter.split('\n');
387
+ for (const line of lines) {
388
+ const trimmed = line.trim();
389
+ if (trimmed.startsWith('description:')) {
390
+ description = trimmed.substring(12).trim();
391
+ break;
392
+ }
393
+ }
394
+
395
+ let toml = '';
396
+ if (description) toml += `description = ${JSON.stringify(description)}\n`;
397
+ toml += `prompt = ${JSON.stringify(body)}\n`;
398
+ return toml;
399
+ }
400
+
401
+ // ─── Skill/Agent Builders ───────────────────────────────────────────────────
402
+
403
+ /** Generate Codex skill adapter header */
404
+ function getCodexSkillAdapterHeader(skillName) {
405
+ const invocation = `$${skillName}`;
406
+ return `<codex_skill_adapter>
407
+ Codex skills-first mode:
408
+ - This skill is invoked by mentioning \`${invocation}\`.
409
+ - Treat all user text after \`${invocation}\` as \`{{PAN_ARGS}}\`.
410
+ - If no arguments are present, treat \`{{PAN_ARGS}}\` as empty.
411
+
412
+ Legacy orchestration compatibility:
413
+ - Any \`Task(...)\` pattern in referenced workflow docs is legacy syntax.
414
+ - Implement equivalent behavior with Codex collaboration tools: \`spawn_agent\`, \`wait\`, \`send_input\`, and \`close_agent\`.
415
+ - Treat legacy \`subagent_type\` names as role hints in the spawned message.
416
+ </codex_skill_adapter>`;
417
+ }
418
+
419
+ /** Claude command → Codex SKILL.md */
420
+ function convertClaudeCommandToCodexSkill(content, skillName) {
421
+ const converted = convertClaudeToCodexMarkdown(content);
422
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
423
+ let description = `Run PAN workflow ${skillName}.`;
424
+ if (frontmatter) {
425
+ const maybeDescription = extractFrontmatterField(frontmatter, 'description');
426
+ if (maybeDescription) description = maybeDescription;
427
+ }
428
+ description = toSingleLine(description);
429
+ const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
430
+ const adapter = getCodexSkillAdapterHeader(skillName);
431
+ return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
432
+ }
433
+
434
+ /** Generate Copilot CLI skill adapter header */
435
+ function getCopilotSkillAdapterHeader(skillName) {
436
+ const invocation = `/pan-${skillName.replace(/^pan-/, '')}`;
437
+ return `<copilot_skill_adapter>
438
+ Copilot CLI skill integration:
439
+ - This skill is invoked via \`${invocation}\`.
440
+ - Treat all user text after \`${invocation}\` as arguments.
441
+ - If no arguments are present, proceed with defaults.
442
+
443
+ Agent orchestration:
444
+ - Any \`Task(...)\` pattern in referenced workflow docs is legacy syntax.
445
+ - Use Copilot CLI's native \`/agent\` command to delegate to sub-agents.
446
+ - Treat legacy \`subagent_type\` names as the agent to invoke.
447
+
448
+ User interaction:
449
+ - When presenting choices to the user, use numbered lists (1. **Option** — description).
450
+ - For single-select questions: ask one question, show numbered options, then say "Type a number or label to choose. Or type your own answer."
451
+ - For multi-select questions: show numbered options, then say "Type the numbers you want, separated by commas (e.g., 1,3). Or type your own answer."
452
+ - Always ask one question at a time. Wait for the user's response before continuing.
453
+ - Accept numbers ("1"), labels ("Option A"), or free-text descriptions as valid answers.
454
+ - Mark the recommended option with **(recommended)** in bold.
455
+ </copilot_skill_adapter>`;
456
+ }
457
+
458
+ /** Claude command → Copilot SKILL.md */
459
+ function convertClaudeCommandToCopilotSkill(content, skillName) {
460
+ const converted = convertClaudeToCopilotMarkdown(content);
461
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
462
+ let description = `Run PAN workflow ${skillName}.`;
463
+ if (frontmatter) {
464
+ const maybeDescription = extractFrontmatterField(frontmatter, 'description');
465
+ if (maybeDescription) description = maybeDescription;
466
+ }
467
+ description = toSingleLine(description);
468
+ const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
469
+ const adapter = getCopilotSkillAdapterHeader(skillName);
470
+ return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
471
+ }
472
+
473
+ /** Claude agent → Copilot .agent.md */
474
+ function convertClaudeToCopilotAgent(content) {
475
+ const converted = convertClaudeToCopilotMarkdown(content);
476
+ const { frontmatter, body } = extractFrontmatterAndBody(converted);
477
+ let name = '';
478
+ let description = '';
479
+ const copilotTools = [];
480
+
481
+ if (frontmatter) {
482
+ name = extractFrontmatterField(frontmatter, 'name') || '';
483
+ description = extractFrontmatterField(frontmatter, 'description') || '';
484
+ const toolsMatch = frontmatter.match(/allowed_tools:\s*\n((?:\s*-\s*.+\n?)*)/);
485
+ if (toolsMatch) {
486
+ const toolLines = toolsMatch[1].match(/^\s*-\s*(.+)$/gm) || [];
487
+ for (const line of toolLines) {
488
+ const toolName = line.replace(/^\s*-\s*/, '').trim();
489
+ const copilotTool = convertCopilotToolName(toolName);
490
+ if (copilotTool && !copilotTools.includes(copilotTool)) {
491
+ copilotTools.push(copilotTool);
492
+ }
493
+ }
494
+ }
495
+ }
496
+
497
+ const toolsYaml = copilotTools.length > 0
498
+ ? `\ntools:\n${copilotTools.map(t => ` - ${yamlQuote(t)}`).join('\n')}`
499
+ : '';
500
+ return `---\nname: ${yamlQuote(name)}\ndescription: ${yamlQuote(description)}${toolsYaml}\n---\n${body}`;
501
+ }
502
+
503
+ // ─── Attribution Processing ─────────────────────────────────────────────────
504
+
505
+ /**
506
+ * Process Co-Authored-By lines based on attribution setting.
507
+ * @param {string} content - File content
508
+ * @param {null|undefined|string} attribution - null=remove, undefined=keep, string=replace
509
+ */
510
+ function processAttribution(content, attribution) {
511
+ if (attribution === null) {
512
+ return content.replace(/(\r?\n){2}Co-Authored-By:.*$/gim, '');
513
+ }
514
+ if (attribution === undefined) return content;
515
+ const safeAttribution = attribution.replace(/\$/g, '$$$$');
516
+ return content.replace(/Co-Authored-By:.*$/gim, `Co-Authored-By: ${safeAttribution}`);
517
+ }
518
+
519
+ // ─── JSONC Parser ───────────────────────────────────────────────────────────
520
+
521
+ /** Parse JSON with comments (JSONC). Strips single-line and block comments, trailing commas. */
522
+ function parseJsonc(content) {
523
+ if (content.charCodeAt(0) === 0xFEFF) {
524
+ content = content.slice(1);
525
+ }
526
+
527
+ let result = '';
528
+ let inString = false;
529
+ let i = 0;
530
+ while (i < content.length) {
531
+ const char = content[i];
532
+ const next = content[i + 1];
533
+
534
+ if (inString) {
535
+ result += char;
536
+ if (char === '\\' && i + 1 < content.length) {
537
+ result += next;
538
+ i += 2;
539
+ continue;
540
+ }
541
+ if (char === '"') inString = false;
542
+ i++;
543
+ } else {
544
+ if (char === '"') {
545
+ inString = true;
546
+ result += char;
547
+ i++;
548
+ } else if (char === '/' && next === '/') {
549
+ while (i < content.length && content[i] !== '\n') i++;
550
+ } else if (char === '/' && next === '*') {
551
+ i += 2;
552
+ while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/')) i++;
553
+ i += 2;
554
+ } else {
555
+ result += char;
556
+ i++;
557
+ }
558
+ }
559
+ }
560
+
561
+ result = result.replace(/,(\s*[}\]])/g, '$1');
562
+ return JSON.parse(result);
563
+ }
564
+
565
+ // ─── Build Hook Command ─────────────────────────────────────────────────────
566
+
567
+ /** Build a hook command path with forward slashes for cross-platform compat */
568
+ function buildHookCommand(configDir, hookName) {
569
+ const hooksPath = configDir.replace(/\\/g, '/') + '/hooks/' + hookName;
570
+ return `node "${hooksPath}"`;
571
+ }
572
+
573
+ // ─── Exports ────────────────────────────────────────────────────────────────
574
+
575
+ module.exports = {
576
+ // Constants
577
+ colorNameToHex,
578
+ claudeToOpencodeTools,
579
+ claudeToGeminiTools,
580
+ claudeToCopilotTools,
581
+ // Utilities
582
+ getDirName,
583
+ getConfigDirFromHome,
584
+ expandTilde,
585
+ toSingleLine,
586
+ yamlQuote,
587
+ buildHookCommand,
588
+ // Frontmatter
589
+ extractFrontmatterAndBody,
590
+ extractFrontmatterField,
591
+ // Tool name converters
592
+ convertToolName,
593
+ convertGeminiToolName,
594
+ convertCopilotToolName,
595
+ // Slash command converters
596
+ convertSlashCommandsToCodexSkillMentions,
597
+ convertSlashCommandsToCopilotSkillMentions,
598
+ // Content converters
599
+ convertClaudeToCodexMarkdown,
600
+ convertClaudeToCopilotMarkdown,
601
+ convertClaudeToOpencodeFrontmatter,
602
+ convertClaudeToGeminiToml,
603
+ convertClaudeToGeminiAgent,
604
+ rewriteAskUserQuestionForCopilot,
605
+ stripSubTags,
606
+ // Skill/agent builders
607
+ getCodexSkillAdapterHeader,
608
+ convertClaudeCommandToCodexSkill,
609
+ getCopilotSkillAdapterHeader,
610
+ convertClaudeCommandToCopilotSkill,
611
+ convertClaudeToCopilotAgent,
612
+ // Attribution
613
+ processAttribution,
614
+ // JSONC
615
+ parseJsonc,
616
+ };