@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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts +3 -0
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +134 -9
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/pennyfarthing-dist/agents/sm-setup.md +37 -2
- package/pennyfarthing-dist/agents/sm.md +68 -22
- package/pennyfarthing-dist/agents/workflow-status-check.md +11 -1
- package/pennyfarthing-dist/commands/git-cleanup.md +43 -308
- package/pennyfarthing-dist/commands/solo.md +31 -0
- package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +1 -1
- package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +83 -83
- package/pennyfarthing-dist/personas/themes/the-expanse.yaml +11 -11
- package/pennyfarthing-dist/scripts/core/agent-session.sh +2 -2
- package/pennyfarthing-dist/scripts/core/check-context.sh +3 -0
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
- package/pennyfarthing-dist/scripts/core/prime.sh +3 -157
- package/pennyfarthing-dist/scripts/core/run.sh +9 -0
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +117 -20
- package/pennyfarthing-dist/scripts/jira/README.md +10 -7
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +13 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +226 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +6 -5
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +319 -0
- package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +6 -5
- package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +270 -0
- package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +59 -0
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +8 -6
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +402 -0
- package/pennyfarthing-dist/scripts/workflow/check.sh +3 -476
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +61 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +13 -0
- package/pennyfarthing-dist/skills/judge/SKILL.md +57 -0
- package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +4 -22
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +83 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +116 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +210 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +88 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +71 -0
- package/pennyfarthing-dist/workflows/git-cleanup.yaml +59 -0
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.mjs +0 -393
- package/pennyfarthing-dist/scripts/hooks/tests/question-reflector.test.mjs +0 -545
- package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.mjs +0 -327
- package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.test.mjs +0 -503
- package/pennyfarthing-dist/scripts/jira/jira-lib.mjs +0 -443
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.mjs +0 -208
- package/pennyfarthing-dist/scripts/jira/jira-sync.mjs +0 -198
- package/pennyfarthing-dist/scripts/misc/add-short-names.mjs +0 -264
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.mjs +0 -474
- package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.mjs +0 -377
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.js +0 -492
- /package/pennyfarthing-dist/guides/{AGENT-COORDINATION.md → agent-coordination.md} +0 -0
- /package/pennyfarthing-dist/guides/{HOOKS.md → hooks.md} +0 -0
- /package/pennyfarthing-dist/guides/{PROMPT-PATTERNS.md → prompt-patterns.md} +0 -0
- /package/pennyfarthing-dist/guides/{SESSION-ARTIFACTS.md → session-artifacts.md} +0 -0
- /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
|
-
}
|