@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,327 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * jira-bidirectional-sync.mjs - Bidirectional sync between sprint YAML and Jira
4
- *
5
- * Story: MSSCI-11842
6
- *
7
- * Usage: node jira-bidirectional-sync.mjs [options]
8
- *
9
- * Options:
10
- * --dry-run Show changes without applying
11
- * --yaml-wins Prefer YAML values on conflict (default: Jira wins)
12
- * --status Sync status field
13
- * --points Sync story points
14
- * --all Sync all fields (status + points)
15
- * --sprint <id> Target specific sprint (default: current)
16
- */
17
-
18
- import {
19
- mapStatusToJira,
20
- mapJiraToStatus,
21
- extractJiraKey
22
- } from './jira-lib.mjs';
23
-
24
- /**
25
- * Parse CLI arguments
26
- * @param {string[]} argv - Command line arguments (without node and script path)
27
- * @returns {object} Parsed arguments
28
- */
29
- export function parseCliArgs(argv) {
30
- const args = {
31
- dryRun: false,
32
- yamlWins: false,
33
- syncStatus: false,
34
- syncPoints: false,
35
- sprintId: null
36
- };
37
-
38
- for (let i = 0; i < argv.length; i++) {
39
- const arg = argv[i];
40
-
41
- switch (arg) {
42
- case '--dry-run':
43
- args.dryRun = true;
44
- break;
45
- case '--yaml-wins':
46
- args.yamlWins = true;
47
- break;
48
- case '--status':
49
- args.syncStatus = true;
50
- break;
51
- case '--points':
52
- args.syncPoints = true;
53
- break;
54
- case '--all':
55
- args.syncStatus = true;
56
- args.syncPoints = true;
57
- break;
58
- case '--sprint':
59
- args.sprintId = argv[++i];
60
- break;
61
- }
62
- }
63
-
64
- return args;
65
- }
66
-
67
- /**
68
- * Generate a sync plan comparing YAML and Jira stories
69
- *
70
- * @param {Array} yamlStories - Stories from sprint YAML [{id, status, points, ...}]
71
- * @param {Array} jiraStories - Stories from Jira [{key, fields: {status: {name}, customfield_10031, ...}}]
72
- * @param {object} options - Sync options
73
- * @param {boolean} options.syncStatus - Whether to sync status field
74
- * @param {boolean} options.syncPoints - Whether to sync points field
75
- * @param {string} options.direction - 'yaml-to-jira', 'jira-to-yaml', or 'bidirectional'
76
- * @param {boolean} options.yamlWins - If true, YAML wins conflicts (default: Jira wins)
77
- * @param {boolean} options.reportMissing - If true, include detailed reports for missing stories
78
- * @param {Date} options.lastSyncTime - Last sync timestamp for conflict detection
79
- * @returns {object} Sync plan with changes, yamlOnly, jiraOnly, both, conflicts, reports
80
- */
81
- export function generateSyncPlan(yamlStories, jiraStories, options = {}) {
82
- const {
83
- syncStatus = false,
84
- syncPoints = false,
85
- direction = 'bidirectional',
86
- yamlWins = false,
87
- reportMissing = false,
88
- lastSyncTime = null
89
- } = options;
90
-
91
- // Build lookup maps
92
- const yamlByKey = new Map();
93
- for (const story of yamlStories) {
94
- yamlByKey.set(story.id, story);
95
- }
96
-
97
- const jiraByKey = new Map();
98
- for (const story of jiraStories) {
99
- jiraByKey.set(story.key, story);
100
- }
101
-
102
- // Categorize stories
103
- const yamlKeys = new Set(yamlStories.map(s => s.id));
104
- const jiraKeys = new Set(jiraStories.map(s => s.key));
105
-
106
- const yamlOnly = [...yamlKeys].filter(k => !jiraKeys.has(k));
107
- const jiraOnly = [...jiraKeys].filter(k => !yamlKeys.has(k));
108
- const both = [...yamlKeys].filter(k => jiraKeys.has(k));
109
-
110
- // Generate changes for stories in both systems
111
- const changes = [];
112
- const conflicts = [];
113
-
114
- for (const key of both) {
115
- const yamlStory = yamlByKey.get(key);
116
- const jiraStory = jiraByKey.get(key);
117
-
118
- const yamlStatus = yamlStory.status;
119
- const jiraStatus = jiraStory.fields?.status?.name;
120
- const yamlPoints = yamlStory.points;
121
- const jiraPoints = jiraStory.fields?.customfield_10031;
122
-
123
- // Normalize statuses for comparison
124
- const normalizedYamlStatus = mapStatusToJira(yamlStatus);
125
- const normalizedJiraStatus = jiraStatus;
126
-
127
- // Check status differences
128
- if (syncStatus && normalizedYamlStatus !== normalizedJiraStatus) {
129
- // Determine action based on direction and yamlWins flag
130
- let action;
131
- let targetStatus;
132
-
133
- if (direction === 'yaml-to-jira') {
134
- action = 'update-jira';
135
- targetStatus = normalizedYamlStatus;
136
- } else if (direction === 'jira-to-yaml') {
137
- action = 'update-yaml';
138
- targetStatus = mapJiraToStatus(jiraStatus);
139
- } else {
140
- // Bidirectional - use yamlWins flag to determine winner
141
- if (yamlWins) {
142
- action = 'update-jira';
143
- targetStatus = normalizedYamlStatus;
144
- } else {
145
- action = 'update-yaml';
146
- targetStatus = mapJiraToStatus(jiraStatus);
147
- }
148
- }
149
-
150
- changes.push({
151
- key,
152
- field: 'status',
153
- action,
154
- yamlStatus,
155
- jiraStatus,
156
- targetStatus
157
- });
158
- }
159
-
160
- // Check points differences
161
- if (syncPoints && yamlPoints !== jiraPoints) {
162
- let action;
163
- let targetPoints;
164
-
165
- if (direction === 'yaml-to-jira') {
166
- action = 'update-jira';
167
- targetPoints = yamlPoints;
168
- } else if (direction === 'jira-to-yaml') {
169
- action = 'update-yaml';
170
- targetPoints = jiraPoints;
171
- } else {
172
- // Bidirectional - use yamlWins flag
173
- if (yamlWins) {
174
- action = 'update-jira';
175
- targetPoints = yamlPoints;
176
- } else {
177
- action = 'update-yaml';
178
- targetPoints = jiraPoints;
179
- }
180
- }
181
-
182
- changes.push({
183
- key,
184
- field: 'points',
185
- action,
186
- yamlPoints,
187
- jiraPoints,
188
- targetPoints
189
- });
190
- }
191
- }
192
-
193
- // Build result
194
- const plan = {
195
- changes,
196
- yamlOnly,
197
- jiraOnly,
198
- both,
199
- conflicts
200
- };
201
-
202
- // Add reports if requested
203
- if (reportMissing) {
204
- plan.reports = {
205
- yamlOnly: yamlOnly.map(key => ({
206
- key,
207
- story: yamlByKey.get(key),
208
- recommendation: 'Create in Jira'
209
- })),
210
- jiraOnly: jiraOnly.map(key => ({
211
- key,
212
- story: jiraByKey.get(key),
213
- recommendation: 'Import to YAML'
214
- }))
215
- };
216
- }
217
-
218
- return plan;
219
- }
220
-
221
- /**
222
- * Execute a sync plan
223
- *
224
- * @param {object} plan - Sync plan from generateSyncPlan
225
- * @param {object} options - Execution options
226
- * @param {boolean} options.dryRun - If true, don't apply changes
227
- * @returns {Promise<object>} Execution result
228
- */
229
- export async function executeSyncPlan(plan, options = {}) {
230
- const { dryRun = false } = options;
231
-
232
- const result = {
233
- dryRun,
234
- wouldApply: plan.changes,
235
- applied: 0,
236
- yamlModified: false,
237
- jiraApiCalls: 0
238
- };
239
-
240
- if (dryRun) {
241
- // In dry-run mode, don't apply any changes
242
- return result;
243
- }
244
-
245
- // Apply changes (actual implementation would go here)
246
- for (const change of plan.changes) {
247
- if (change.action === 'update-yaml') {
248
- // TODO: Update YAML file
249
- result.yamlModified = true;
250
- } else if (change.action === 'update-jira') {
251
- // TODO: Call Jira API
252
- result.jiraApiCalls++;
253
- }
254
- result.applied++;
255
- }
256
-
257
- return result;
258
- }
259
-
260
- /**
261
- * Format sync plan as human-readable string
262
- *
263
- * @param {object} plan - Sync plan from generateSyncPlan
264
- * @returns {string} Human-readable output
265
- */
266
- export function formatSyncPlan(plan) {
267
- const lines = [];
268
-
269
- lines.push('='.repeat(60));
270
- lines.push('Bidirectional Sync Plan');
271
- lines.push('='.repeat(60));
272
- lines.push('');
273
-
274
- // Summary
275
- lines.push(`Stories in YAML only: ${plan.yamlOnly.length}`);
276
- lines.push(`Stories in Jira only: ${plan.jiraOnly.length}`);
277
- lines.push(`Stories in both: ${plan.both.length}`);
278
- lines.push(`Changes to apply: ${plan.changes.length}`);
279
- lines.push('');
280
-
281
- // YAML-only stories
282
- if (plan.yamlOnly.length > 0) {
283
- lines.push('--- YAML Only (not in Jira) ---');
284
- for (const key of plan.yamlOnly) {
285
- lines.push(` ${key}`);
286
- }
287
- lines.push('');
288
- }
289
-
290
- // Jira-only stories
291
- if (plan.jiraOnly.length > 0) {
292
- lines.push('--- Jira Only (not in YAML) ---');
293
- for (const key of plan.jiraOnly) {
294
- lines.push(` ${key}`);
295
- }
296
- lines.push('');
297
- }
298
-
299
- // Changes
300
- if (plan.changes.length > 0) {
301
- lines.push('--- Changes ---');
302
- for (const change of plan.changes) {
303
- const direction = change.action === 'update-yaml' ? 'Jira → YAML' : 'YAML → Jira';
304
- if (change.field === 'status') {
305
- lines.push(` ${change.key}: status ${direction}`);
306
- lines.push(` YAML: ${change.yamlStatus} | Jira: ${change.jiraStatus} → ${change.targetStatus}`);
307
- } else if (change.field === 'points') {
308
- lines.push(` ${change.key}: points ${direction}`);
309
- lines.push(` YAML: ${change.yamlPoints} | Jira: ${change.jiraPoints} → ${change.targetPoints}`);
310
- }
311
- }
312
- lines.push('');
313
- }
314
-
315
- // Conflicts
316
- if (plan.conflicts && plan.conflicts.length > 0) {
317
- lines.push('--- Conflicts (manual resolution needed) ---');
318
- for (const conflict of plan.conflicts) {
319
- lines.push(` ${conflict.key}: ${conflict.field}`);
320
- }
321
- lines.push('');
322
- }
323
-
324
- lines.push('='.repeat(60));
325
-
326
- return lines.join('\n');
327
- }