@pennyfarthing/core 7.7.0 → 7.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 (58) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/doctor.d.ts +3 -0
  4. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/doctor.js +134 -9
  6. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  7. package/pennyfarthing-dist/agents/sm-setup.md +37 -2
  8. package/pennyfarthing-dist/agents/sm.md +68 -22
  9. package/pennyfarthing-dist/agents/workflow-status-check.md +11 -1
  10. package/pennyfarthing-dist/commands/git-cleanup.md +43 -308
  11. package/pennyfarthing-dist/commands/solo.md +31 -0
  12. package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +1 -1
  13. package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +83 -83
  14. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +11 -11
  15. package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -2
  16. package/pennyfarthing-dist/scripts/core/check-context.sh +3 -0
  17. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
  18. package/pennyfarthing-dist/scripts/core/prime.sh +3 -157
  19. package/pennyfarthing-dist/scripts/core/run.sh +9 -0
  20. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  21. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +117 -20
  22. package/pennyfarthing-dist/scripts/jira/README.md +10 -7
  23. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +13 -0
  24. package/pennyfarthing-dist/scripts/misc/add_short_names.py +226 -0
  25. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +6 -5
  26. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +319 -0
  27. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +6 -5
  28. package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +270 -0
  29. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +59 -0
  30. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +8 -6
  31. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +402 -0
  32. package/pennyfarthing-dist/scripts/workflow/check.sh +3 -476
  33. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +61 -0
  34. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +13 -0
  35. package/pennyfarthing-dist/skills/judge/SKILL.md +57 -0
  36. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +4 -22
  37. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +83 -0
  38. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +116 -0
  39. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +210 -0
  40. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +88 -0
  41. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +71 -0
  42. package/pennyfarthing-dist/workflows/git-cleanup.yaml +59 -0
  43. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.mjs +0 -393
  44. package/pennyfarthing-dist/scripts/hooks/tests/question-reflector.test.mjs +0 -545
  45. package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.mjs +0 -327
  46. package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.test.mjs +0 -503
  47. package/pennyfarthing-dist/scripts/jira/jira-lib.mjs +0 -443
  48. package/pennyfarthing-dist/scripts/jira/jira-sync-story.mjs +0 -208
  49. package/pennyfarthing-dist/scripts/jira/jira-sync.mjs +0 -198
  50. package/pennyfarthing-dist/scripts/misc/add-short-names.mjs +0 -264
  51. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.mjs +0 -474
  52. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.mjs +0 -377
  53. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.js +0 -492
  54. /package/pennyfarthing-dist/guides/{AGENT-COORDINATION.md → agent-coordination.md} +0 -0
  55. /package/pennyfarthing-dist/guides/{HOOKS.md → hooks.md} +0 -0
  56. /package/pennyfarthing-dist/guides/{PROMPT-PATTERNS.md → prompt-patterns.md} +0 -0
  57. /package/pennyfarthing-dist/guides/{SESSION-ARTIFACTS.md → session-artifacts.md} +0 -0
  58. /package/pennyfarthing-dist/guides/{XML-TAGS.md → xml-tags.md} +0 -0
@@ -1,474 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * migrate-bmad-workflow.mjs - Migrate BMAD workflows to Pennyfarthing format
4
- *
5
- * Story MSSCI-12132: BMAD to Pennyfarthing migration script
6
- *
7
- * Converts BMAD workflow format to Pennyfarthing stepped workflow format:
8
- * - Parses BMAD workflow.md with YAML frontmatter
9
- * - Extracts step files and their frontmatter
10
- * - Converts variable syntax ({var-name} → {var_name})
11
- * - Generates workflow.yaml with Pennyfarthing schema
12
- * - Preserves tri-modal structure (steps-c, steps-v, steps-e)
13
- *
14
- * Usage: node migrate-bmad-workflow.mjs [--dry-run] <source-dir> [target-dir]
15
- */
16
-
17
- import fs from 'fs';
18
- import path from 'path';
19
- import { fileURLToPath } from 'url';
20
-
21
- const __filename = fileURLToPath(import.meta.url);
22
- const __dirname = path.dirname(__filename);
23
-
24
- // =============================================================================
25
- // Colors
26
- // =============================================================================
27
-
28
- const colors = {
29
- red: (s) => `\x1b[31m${s}\x1b[0m`,
30
- green: (s) => `\x1b[32m${s}\x1b[0m`,
31
- yellow: (s) => `\x1b[33m${s}\x1b[0m`,
32
- blue: (s) => `\x1b[34m${s}\x1b[0m`,
33
- };
34
-
35
- const log = {
36
- info: (msg) => console.log(`${colors.blue('INFO')}: ${msg}`),
37
- success: (msg) => console.log(`${colors.green('SUCCESS')}: ${msg}`),
38
- warn: (msg) => console.log(`${colors.yellow('WARN')}: ${msg}`),
39
- error: (msg) => console.error(`${colors.red('ERROR')}: ${msg}`),
40
- };
41
-
42
- // =============================================================================
43
- // YAML Frontmatter Parsing
44
- // =============================================================================
45
-
46
- /**
47
- * Extract YAML frontmatter from markdown content
48
- * Returns { frontmatter: object, content: string }
49
- */
50
- function extractFrontmatter(content) {
51
- const lines = content.split('\n');
52
-
53
- // Check for opening ---
54
- if (lines[0]?.trim() !== '---') {
55
- return { frontmatter: {}, content };
56
- }
57
-
58
- // Find closing ---
59
- let endIndex = -1;
60
- for (let i = 1; i < lines.length; i++) {
61
- if (lines[i].trim() === '---') {
62
- endIndex = i;
63
- break;
64
- }
65
- }
66
-
67
- if (endIndex === -1) {
68
- return { frontmatter: {}, content };
69
- }
70
-
71
- // Parse frontmatter as simple key-value pairs
72
- const frontmatterLines = lines.slice(1, endIndex);
73
- const frontmatter = {};
74
-
75
- for (const line of frontmatterLines) {
76
- const match = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\s*(.*)$/);
77
- if (match) {
78
- let [, key, value] = match;
79
- // Remove quotes
80
- value = value.replace(/^['"]|['"]$/g, '').trim();
81
- frontmatter[key] = value;
82
- }
83
- }
84
-
85
- // Rest of content
86
- const restContent = lines.slice(endIndex + 1).join('\n');
87
-
88
- return { frontmatter, content: restContent };
89
- }
90
-
91
- // =============================================================================
92
- // Variable Conversion
93
- // =============================================================================
94
-
95
- /**
96
- * Convert BMAD-style dashed variables to Pennyfarthing underscore style
97
- * {var-name} → {var_name}
98
- */
99
- function convertVariables(text) {
100
- // Repeatedly convert dashed variables until none remain
101
- // This handles multi-segment variables like {user-skill-level}
102
- let result = text;
103
- let prev;
104
-
105
- do {
106
- prev = result;
107
- result = result.replace(/\{([a-zA-Z0-9_]+)-([a-zA-Z0-9_-]+)\}/g, (match, p1, p2) => {
108
- // Convert all dashes in p2 to underscores
109
- const converted = p2.replace(/-/g, '_');
110
- return `{${p1}_${converted}}`;
111
- });
112
- } while (result !== prev);
113
-
114
- return result;
115
- }
116
-
117
- // =============================================================================
118
- // Directory Helpers
119
- // =============================================================================
120
-
121
- /**
122
- * Check if workflow is tri-modal (has steps-c, steps-v, or steps-e)
123
- */
124
- function isTrimodal(sourceDir) {
125
- return (
126
- fs.existsSync(path.join(sourceDir, 'steps-c')) ||
127
- fs.existsSync(path.join(sourceDir, 'steps-v')) ||
128
- fs.existsSync(path.join(sourceDir, 'steps-e'))
129
- );
130
- }
131
-
132
- /**
133
- * Get step directories to process
134
- */
135
- function getStepDirectories(sourceDir) {
136
- const dirs = [];
137
-
138
- if (isTrimodal(sourceDir)) {
139
- for (const dir of ['steps-c', 'steps-v', 'steps-e']) {
140
- if (fs.existsSync(path.join(sourceDir, dir))) {
141
- dirs.push(dir);
142
- }
143
- }
144
- } else if (fs.existsSync(path.join(sourceDir, 'steps'))) {
145
- dirs.push('steps');
146
- }
147
-
148
- return dirs;
149
- }
150
-
151
- /**
152
- * Get supporting directories (templates, data, etc.)
153
- */
154
- function getSupportingDirectories(sourceDir) {
155
- const dirs = [];
156
-
157
- for (const dir of ['templates', 'data', 'assets']) {
158
- if (fs.existsSync(path.join(sourceDir, dir))) {
159
- dirs.push(dir);
160
- }
161
- }
162
-
163
- return dirs;
164
- }
165
-
166
- /**
167
- * Get root-level template files (e.g., project-context-template.md)
168
- */
169
- function getRootTemplateFiles(sourceDir) {
170
- const files = [];
171
-
172
- try {
173
- const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
174
- for (const entry of entries) {
175
- if (entry.isFile() && entry.name.includes('-template') && entry.name.endsWith('.md')) {
176
- files.push(entry.name);
177
- }
178
- }
179
- } catch {
180
- // Ignore errors
181
- }
182
-
183
- return files;
184
- }
185
-
186
- /**
187
- * Recursively copy directory with file transformation
188
- */
189
- function copyDirWithTransform(srcDir, destDir, transform, dryRun) {
190
- if (!fs.existsSync(srcDir)) return;
191
-
192
- if (!dryRun) {
193
- fs.mkdirSync(destDir, { recursive: true });
194
- }
195
-
196
- const entries = fs.readdirSync(srcDir, { withFileTypes: true });
197
-
198
- for (const entry of entries) {
199
- const srcPath = path.join(srcDir, entry.name);
200
- const destPath = path.join(destDir, entry.name);
201
-
202
- if (entry.isDirectory()) {
203
- copyDirWithTransform(srcPath, destPath, transform, dryRun);
204
- } else if (entry.isFile()) {
205
- if (dryRun) {
206
- const content = fs.readFileSync(srcPath, 'utf-8');
207
- const transformed = transform(content, srcPath);
208
- if (content !== transformed) {
209
- log.info(`[DRY-RUN] Would convert variables in: ${path.relative(process.cwd(), srcPath)}`);
210
- }
211
- } else {
212
- const content = fs.readFileSync(srcPath, 'utf-8');
213
- const transformed = transform(content, srcPath);
214
- fs.writeFileSync(destPath, transformed, 'utf-8');
215
- log.success(`Copied: ${path.relative(destDir, destPath)}`);
216
- }
217
- }
218
- }
219
- }
220
-
221
- // =============================================================================
222
- // Workflow Generation
223
- // =============================================================================
224
-
225
- /**
226
- * Generate workflow.yaml content
227
- */
228
- function generateWorkflowYaml(name, description, trimodal) {
229
- const convertedDesc = convertVariables(description);
230
-
231
- let stepConfig;
232
- let modesConfig = '';
233
-
234
- if (trimodal) {
235
- stepConfig = ` path: ./steps-c/
236
- pattern: step-*.md`;
237
- modesConfig = `
238
- # Tri-modal workflow paths
239
- modes:
240
- create: ./steps-c/
241
- validate: ./steps-v/
242
- edit: ./steps-e/`;
243
- } else {
244
- stepConfig = ` path: ./steps/
245
- pattern: step-*.md`;
246
- }
247
-
248
- return `# ${name} Workflow - Migrated from BMAD format
249
- # Generated by migrate-bmad-workflow.mjs
250
-
251
- workflow:
252
- name: ${name}
253
- description: ${convertedDesc}
254
- version: "1.0.0"
255
- type: stepped
256
-
257
- # Step configuration
258
- steps:
259
- ${stepConfig}
260
- ${modesConfig}
261
- # Variables available in step files
262
- variables:
263
- project_root: .
264
- planning_artifacts: ./artifacts
265
- output_file: artifacts/${name}.md
266
-
267
- # Agent assignment (customize as needed)
268
- agent: architect
269
-
270
- # Triggers - when to suggest this workflow
271
- triggers:
272
- types: [${name}]
273
- tags: [${name}, stepped]
274
- `;
275
- }
276
-
277
- // =============================================================================
278
- // Main
279
- // =============================================================================
280
-
281
- function showHelp() {
282
- console.log(`
283
- Usage: migrate-bmad-workflow.mjs [--dry-run] <source-dir> [target-dir]
284
-
285
- Migrate BMAD workflows to Pennyfarthing stepped workflow format.
286
-
287
- Arguments:
288
- source-dir Path to BMAD workflow directory (must contain workflow.md)
289
- target-dir Path to output directory (default: current directory)
290
-
291
- Options:
292
- --dry-run Show what would be done without making changes
293
- -h, --help Show this help message
294
-
295
- Examples:
296
- # Migrate PRD workflow to ./prd/
297
- migrate-bmad-workflow.mjs ~/BMAD-METHOD/workflows/prd ./prd
298
-
299
- # Preview migration without writing files
300
- migrate-bmad-workflow.mjs --dry-run ~/BMAD-METHOD/workflows/prd ./prd
301
- `);
302
- }
303
-
304
- function main() {
305
- const args = process.argv.slice(2);
306
-
307
- // Parse flags
308
- let dryRun = false;
309
- const positional = [];
310
-
311
- for (const arg of args) {
312
- if (arg === '--dry-run') {
313
- dryRun = true;
314
- } else if (arg === '-h' || arg === '--help') {
315
- showHelp();
316
- process.exit(0);
317
- } else if (!arg.startsWith('-')) {
318
- positional.push(arg);
319
- } else {
320
- log.error(`Unknown option: ${arg}`);
321
- process.exit(1);
322
- }
323
- }
324
-
325
- // Validate arguments
326
- const [sourceDir, targetDir = '.'] = positional;
327
-
328
- if (!sourceDir) {
329
- log.error('Source directory required');
330
- showHelp();
331
- process.exit(1);
332
- }
333
-
334
- if (!fs.existsSync(sourceDir)) {
335
- log.error(`Source directory not found: ${sourceDir}`);
336
- process.exit(1);
337
- }
338
-
339
- const workflowMdPath = path.join(sourceDir, 'workflow.md');
340
- if (!fs.existsSync(workflowMdPath)) {
341
- log.error(`workflow.md not found in ${sourceDir}`);
342
- process.exit(1);
343
- }
344
-
345
- // Header
346
- log.info('BMAD Workflow Migration');
347
- log.info('=======================');
348
- log.info(`Source: ${sourceDir}`);
349
- log.info(`Target: ${targetDir}`);
350
-
351
- if (dryRun) {
352
- log.warn('DRY-RUN MODE - No changes will be made');
353
- }
354
-
355
- console.log('');
356
-
357
- // Parse workflow.md
358
- const workflowContent = fs.readFileSync(workflowMdPath, 'utf-8');
359
- const { frontmatter } = extractFrontmatter(workflowContent);
360
-
361
- const name = frontmatter.name;
362
- const description = frontmatter.description || '';
363
-
364
- if (!name) {
365
- log.error('Could not extract workflow name from workflow.md');
366
- process.exit(1);
367
- }
368
-
369
- log.info(`Parsed workflow: ${name}`);
370
- log.info(`Description: ${description}`);
371
-
372
- console.log('');
373
-
374
- // Create target directory
375
- if (!dryRun) {
376
- fs.mkdirSync(targetDir, { recursive: true });
377
- }
378
-
379
- // Generate workflow.yaml
380
- log.info('Generating workflow.yaml');
381
-
382
- const trimodal = isTrimodal(sourceDir);
383
- const workflowYaml = generateWorkflowYaml(name, description, trimodal);
384
-
385
- if (dryRun) {
386
- log.info('[DRY-RUN] Would create: workflow.yaml');
387
- } else {
388
- fs.writeFileSync(path.join(targetDir, 'workflow.yaml'), workflowYaml, 'utf-8');
389
- log.success('Created workflow.yaml');
390
- }
391
-
392
- console.log('');
393
-
394
- // Copy step directories
395
- const stepDirs = getStepDirectories(sourceDir);
396
-
397
- for (const dir of stepDirs) {
398
- log.info(`Processing step directory: ${dir}`);
399
-
400
- if (dryRun) {
401
- log.info(`[DRY-RUN] Would copy and transform: ${dir}/`);
402
- }
403
-
404
- copyDirWithTransform(
405
- path.join(sourceDir, dir),
406
- path.join(targetDir, dir),
407
- (content, filePath) => {
408
- // Only convert variables in markdown files
409
- if (filePath.endsWith('.md')) {
410
- return convertVariables(content);
411
- }
412
- return content;
413
- },
414
- dryRun
415
- );
416
- }
417
-
418
- console.log('');
419
-
420
- // Copy supporting directories
421
- const supportDirs = getSupportingDirectories(sourceDir);
422
-
423
- for (const dir of supportDirs) {
424
- log.info(`Processing supporting directory: ${dir}`);
425
-
426
- if (dryRun) {
427
- log.info(`[DRY-RUN] Would copy: ${dir}/`);
428
- }
429
-
430
- copyDirWithTransform(
431
- path.join(sourceDir, dir),
432
- path.join(targetDir, dir),
433
- (content, filePath) => {
434
- if (filePath.endsWith('.md')) {
435
- return convertVariables(content);
436
- }
437
- return content;
438
- },
439
- dryRun
440
- );
441
- }
442
-
443
- console.log('');
444
-
445
- // Copy root-level template files
446
- const templateFiles = getRootTemplateFiles(sourceDir);
447
-
448
- for (const file of templateFiles) {
449
- log.info(`Processing root template file: ${file}`);
450
-
451
- const srcPath = path.join(sourceDir, file);
452
- const destPath = path.join(targetDir, file);
453
-
454
- if (dryRun) {
455
- log.info(`[DRY-RUN] Would copy: ${file}`);
456
- } else {
457
- const content = fs.readFileSync(srcPath, 'utf-8');
458
- const transformed = convertVariables(content);
459
- fs.writeFileSync(destPath, transformed, 'utf-8');
460
- log.success(`Copied: ${file}`);
461
- }
462
- }
463
-
464
- console.log('');
465
-
466
- if (dryRun) {
467
- log.info('DRY-RUN complete. No files were modified.');
468
- } else {
469
- log.success('Migration complete!');
470
- log.info(`Output directory: ${targetDir}`);
471
- }
472
- }
473
-
474
- main();