@pennyfarthing/core 7.0.2 → 7.4.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/LICENSE +14 -0
- package/README.md +2 -2
- package/package.json +11 -10
- package/packages/core/dist/cli/commands/cyclist.d.ts +2 -2
- package/packages/core/dist/cli/commands/cyclist.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/cyclist.js +8 -9
- package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
- package/packages/core/dist/cli/commands/cyclist.test.js +6 -4
- package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init.js +4 -3
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +15 -139
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/cyclist-migration.test.js +4 -3
- package/packages/core/dist/cli/cyclist-migration.test.js.map +1 -1
- package/packages/core/dist/cli/utils/constants.d.ts +7 -1
- package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/constants.js +4 -2
- package/packages/core/dist/cli/utils/constants.js.map +1 -1
- package/packages/core/dist/cli/utils/node-modules.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/node-modules.js +2 -1
- package/packages/core/dist/cli/utils/node-modules.js.map +1 -1
- package/packages/core/dist/cli/utils/themes.d.ts +1 -4
- package/packages/core/dist/cli/utils/themes.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/themes.js +2 -22
- package/packages/core/dist/cli/utils/themes.js.map +1 -1
- package/packages/core/dist/cli/utils/themes.test.d.ts +3 -3
- package/packages/core/dist/cli/utils/themes.test.js +16 -13
- package/packages/core/dist/cli/utils/themes.test.js.map +1 -1
- package/packages/core/dist/cli/workspace.test.js +11 -9
- package/packages/core/dist/cli/workspace.test.js.map +1 -1
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +10 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/jira/jira-epic-creation.d.ts +109 -0
- package/packages/core/dist/jira/jira-epic-creation.d.ts.map +1 -0
- package/packages/core/dist/jira/jira-epic-creation.js +253 -0
- package/packages/core/dist/jira/jira-epic-creation.js.map +1 -0
- package/packages/core/dist/jira/jira-epic-creation.test.d.ts +16 -0
- package/packages/core/dist/jira/jira-epic-creation.test.d.ts.map +1 -0
- package/packages/core/dist/jira/jira-epic-creation.test.js +387 -0
- package/packages/core/dist/jira/jira-epic-creation.test.js.map +1 -0
- package/packages/core/dist/jira/jira-sprint-sync.d.ts +247 -0
- package/packages/core/dist/jira/jira-sprint-sync.d.ts.map +1 -0
- package/packages/core/dist/jira/jira-sprint-sync.js +670 -0
- package/packages/core/dist/jira/jira-sprint-sync.js.map +1 -0
- package/packages/core/dist/jira/jira-sprint-sync.test.d.ts +16 -0
- package/packages/core/dist/jira/jira-sprint-sync.test.d.ts.map +1 -0
- package/packages/core/dist/jira/jira-sprint-sync.test.js +845 -0
- package/packages/core/dist/jira/jira-sprint-sync.test.js.map +1 -0
- package/packages/core/dist/scripts/generate-spider.d.ts +11 -1
- package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-spider.js +24 -1
- package/packages/core/dist/scripts/generate-spider.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider.test.js +6 -4
- package/packages/core/dist/scripts/generate-spider.test.js.map +1 -1
- package/packages/core/dist/workflow/gate-handler.d.ts +94 -0
- package/packages/core/dist/workflow/gate-handler.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-handler.js +189 -0
- package/packages/core/dist/workflow/gate-handler.js.map +1 -0
- package/packages/core/dist/workflow/gate-handler.test.d.ts +14 -0
- package/packages/core/dist/workflow/gate-handler.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/gate-handler.test.js +543 -0
- package/packages/core/dist/workflow/gate-handler.test.js.map +1 -0
- package/packages/core/dist/workflow/generic-handoff.d.ts +46 -0
- package/packages/core/dist/workflow/generic-handoff.d.ts.map +1 -1
- package/packages/core/dist/workflow/generic-handoff.js +53 -0
- package/packages/core/dist/workflow/generic-handoff.js.map +1 -1
- package/packages/core/dist/workflow/generic-handoff.test.js +2 -2
- package/packages/core/dist/workflow/generic-handoff.test.js.map +1 -1
- package/packages/core/dist/workflow/handoff.d.ts +281 -0
- package/packages/core/dist/workflow/handoff.d.ts.map +1 -0
- package/packages/core/dist/workflow/handoff.js +411 -0
- package/packages/core/dist/workflow/handoff.js.map +1 -0
- package/packages/core/dist/workflow/handoff.test.d.ts +21 -0
- package/packages/core/dist/workflow/handoff.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/handoff.test.js +499 -0
- package/packages/core/dist/workflow/handoff.test.js.map +1 -0
- package/packages/core/dist/workflow/index.d.ts +16 -0
- package/packages/core/dist/workflow/index.d.ts.map +1 -0
- package/packages/core/dist/workflow/index.js +24 -0
- package/packages/core/dist/workflow/index.js.map +1 -0
- package/packages/core/dist/workflow/session-state.d.ts +92 -0
- package/packages/core/dist/workflow/session-state.d.ts.map +1 -0
- package/packages/core/dist/workflow/session-state.js +198 -0
- package/packages/core/dist/workflow/session-state.js.map +1 -0
- package/packages/core/dist/workflow/session-state.test.d.ts +8 -0
- package/packages/core/dist/workflow/session-state.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/session-state.test.js +551 -0
- package/packages/core/dist/workflow/session-state.test.js.map +1 -0
- package/packages/core/dist/workflow/sm-subagents.test.d.ts +2 -2
- package/packages/core/dist/workflow/sm-subagents.test.js +6 -6
- package/packages/core/dist/workflow/sm-subagents.test.js.map +1 -1
- package/packages/core/dist/workflow/step-parser.d.ts +45 -0
- package/packages/core/dist/workflow/step-parser.d.ts.map +1 -0
- package/packages/core/dist/workflow/step-parser.js +147 -0
- package/packages/core/dist/workflow/step-parser.js.map +1 -0
- package/packages/core/dist/workflow/step-parser.test.d.ts +14 -0
- package/packages/core/dist/workflow/step-parser.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/step-parser.test.js +470 -0
- package/packages/core/dist/workflow/step-parser.test.js.map +1 -0
- package/packages/core/dist/workflow/trimodal.d.ts +86 -0
- package/packages/core/dist/workflow/trimodal.d.ts.map +1 -0
- package/packages/core/dist/workflow/trimodal.js +118 -0
- package/packages/core/dist/workflow/trimodal.js.map +1 -0
- package/packages/core/dist/workflow/trimodal.test.d.ts +11 -0
- package/packages/core/dist/workflow/trimodal.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/trimodal.test.js +395 -0
- package/packages/core/dist/workflow/trimodal.test.js.map +1 -0
- package/packages/core/dist/workflow/variable-resolver.d.ts +67 -0
- package/packages/core/dist/workflow/variable-resolver.d.ts.map +1 -0
- package/packages/core/dist/workflow/variable-resolver.js +156 -0
- package/packages/core/dist/workflow/variable-resolver.js.map +1 -0
- package/packages/core/dist/workflow/variable-resolver.test.d.ts +14 -0
- package/packages/core/dist/workflow/variable-resolver.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/variable-resolver.test.js +400 -0
- package/packages/core/dist/workflow/variable-resolver.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-executor.d.ts +163 -0
- package/packages/core/dist/workflow/workflow-executor.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-executor.js +197 -0
- package/packages/core/dist/workflow/workflow-executor.js.map +1 -0
- package/packages/core/dist/workflow/workflow-executor.test.d.ts +8 -0
- package/packages/core/dist/workflow/workflow-executor.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-executor.test.js +444 -0
- package/packages/core/dist/workflow/workflow-executor.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-loader.test.js +5 -5
- package/packages/core/dist/workflow/workflow-loader.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-migration.test.js +8 -9
- package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-permissions.d.ts +55 -0
- package/packages/core/dist/workflow/workflow-permissions.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-permissions.js +64 -0
- package/packages/core/dist/workflow/workflow-permissions.js.map +1 -0
- package/packages/core/dist/workflow/workflow-permissions.test.d.ts +15 -0
- package/packages/core/dist/workflow/workflow-permissions.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-permissions.test.js +301 -0
- package/packages/core/dist/workflow/workflow-permissions.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-schema.d.ts +61 -2
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +293 -69
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.js +6 -6
- package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-stepped-schema.test.d.ts +18 -0
- package/packages/core/dist/workflow/workflow-stepped-schema.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/workflow-stepped-schema.test.js +608 -0
- package/packages/core/dist/workflow/workflow-stepped-schema.test.js.map +1 -0
- package/pennyfarthing-dist/agents/README.md +63 -134
- package/pennyfarthing-dist/agents/architect.md +18 -10
- package/pennyfarthing-dist/agents/dev.md +47 -32
- package/pennyfarthing-dist/agents/devops.md +18 -9
- package/pennyfarthing-dist/agents/handoff.md +289 -0
- package/pennyfarthing-dist/agents/orchestrator.md +44 -22
- package/pennyfarthing-dist/agents/pm.md +13 -8
- package/pennyfarthing-dist/agents/reviewer-preflight.md +42 -128
- package/pennyfarthing-dist/agents/reviewer.md +104 -57
- package/pennyfarthing-dist/agents/sm-file-summary.md +24 -75
- package/pennyfarthing-dist/agents/sm-finish.md +61 -0
- package/pennyfarthing-dist/agents/sm-handoff.md +102 -54
- package/pennyfarthing-dist/agents/sm-setup.md +174 -0
- package/pennyfarthing-dist/agents/sm.md +223 -100
- package/pennyfarthing-dist/agents/tea.md +42 -34
- package/pennyfarthing-dist/agents/tech-writer.md +74 -6
- package/pennyfarthing-dist/agents/testing-runner.md +78 -360
- package/pennyfarthing-dist/agents/ux-designer.md +80 -7
- package/pennyfarthing-dist/agents/workflow-status-check.md +35 -299
- package/pennyfarthing-dist/commands/architect.md +2 -2
- package/pennyfarthing-dist/commands/close-epic.md +5 -2
- package/pennyfarthing-dist/commands/create-branches-from-story.md +7 -23
- package/pennyfarthing-dist/commands/dev.md +2 -2
- package/pennyfarthing-dist/commands/devops.md +3 -3
- package/pennyfarthing-dist/commands/git-cleanup.md +5 -5
- package/pennyfarthing-dist/commands/health-check.md +1 -1
- package/pennyfarthing-dist/commands/list-themes.md +7 -3
- package/pennyfarthing-dist/commands/orchestrator.md +2 -2
- package/pennyfarthing-dist/commands/parallel-work.md +4 -4
- package/pennyfarthing-dist/commands/pm.md +2 -2
- package/pennyfarthing-dist/commands/prime.md +7 -7
- package/pennyfarthing-dist/commands/release.md +1 -1
- package/pennyfarthing-dist/commands/repo-status.md +2 -2
- package/pennyfarthing-dist/commands/retro.md +2 -2
- package/pennyfarthing-dist/commands/reviewer.md +2 -2
- package/pennyfarthing-dist/commands/set-theme.md +5 -1
- package/pennyfarthing-dist/commands/sm.md +2 -2
- package/pennyfarthing-dist/commands/start-epic.md +26 -14
- package/pennyfarthing-dist/commands/sync-epic-to-jira.md +8 -8
- package/pennyfarthing-dist/commands/sync-work-with-sprint.md +1 -4
- package/pennyfarthing-dist/commands/tea.md +2 -2
- package/pennyfarthing-dist/commands/tech-writer.md +2 -2
- package/pennyfarthing-dist/commands/ux-designer.md +3 -3
- package/pennyfarthing-dist/commands/work.md +15 -4
- package/pennyfarthing-dist/commands/workflow.md +21 -0
- package/pennyfarthing-dist/guides/AGENT-COORDINATION.md +15 -15
- package/pennyfarthing-dist/guides/PROMPT-PATTERNS.md +1 -1
- package/pennyfarthing-dist/guides/SESSION-ARTIFACTS.md +4 -4
- package/pennyfarthing-dist/guides/agent-behavior.md +238 -0
- package/pennyfarthing-dist/guides/agent-template-strategic.md +2 -2
- package/pennyfarthing-dist/guides/agent-template-tactical.md +4 -4
- package/pennyfarthing-dist/guides/patterns/approval-gates-pattern.md +1 -1
- package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +17 -17
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +8 -8
- package/pennyfarthing-dist/guides/workflow-schema.md +62 -0
- package/pennyfarthing-dist/guides/worktree-mode.md +5 -5
- package/pennyfarthing-dist/personas/themes/1984.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/a-team.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/agatha-christie.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/alice-in-wonderland.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/all-stars.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/ancient-philosophers.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/ancient-strategists.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/arcane.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/arthurian-mythos.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/avatar-the-last-airbender.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/babylon-5.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/battlestar-galactica.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/better-call-saul.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/big-lebowski.yaml +2 -2
- package/pennyfarthing-dist/personas/themes/black-sails.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/blade-runner.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/bobiverse.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/breaking-bad.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/catch-22.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/classical-composers.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/control.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/count-of-monte-cristo.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/cowboy-bebop.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/deadwood.yaml +2 -2
- package/pennyfarthing-dist/personas/themes/discworld.yaml +2 -2
- package/pennyfarthing-dist/personas/themes/doctor-who.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/enlightenment-thinkers.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/expeditionary-force.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/firefly.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/foundation.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/futurama.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/game-of-thrones.yaml +2 -2
- package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/gothic-literature.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/great-gatsby.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/greek-mythology.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/harry-potter.yaml +2 -2
- package/pennyfarthing-dist/personas/themes/his-dark-materials.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/historical-figures.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/hitchhikers-guide.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/house-md.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/imperial-radch.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/jane-austen.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/jazz-legends.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/justified.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/legion-of-doom.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/les-miserables.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/lovecraft-mythos.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/mad-men.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/marvel-mcu.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/mash.yaml +69 -66
- package/pennyfarthing-dist/personas/themes/mass-effect.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/military-commanders.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/moby-dick.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/monty-python.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/neuromancer.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/norse-mythology.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/peaky-blinders.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/princess-bride.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/rome.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/russian-masters.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/sandman.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/shakespeare.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/sherlock-holmes.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/snow-crash.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/software-pioneers.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/star-trek-tng.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/star-trek-tos.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/star-wars.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/superfriends.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-americans.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-crown.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-expanse.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-good-place.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-office.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-simpsons.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-sopranos.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-wire.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/the-witcher.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/vorkosigan-saga.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/west-wing.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/wwii-leaders.yaml +1 -1
- package/pennyfarthing-dist/scripts/README.md +68 -0
- package/pennyfarthing-dist/scripts/core/README.md +26 -0
- package/pennyfarthing-dist/scripts/{agent-session.sh → core/agent-session.sh} +34 -23
- package/pennyfarthing-dist/scripts/{check-context.sh → core/check-context.sh} +93 -37
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +90 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +136 -0
- package/pennyfarthing-dist/scripts/core/run.sh +75 -0
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +21 -0
- package/pennyfarthing-dist/scripts/git/README.md +25 -0
- package/pennyfarthing-dist/scripts/{utils → git}/create-feature-branches.sh +11 -15
- package/pennyfarthing-dist/scripts/{utils → git}/git-status-all.sh +1 -1
- package/pennyfarthing-dist/scripts/{install-git-hooks.sh → git/install-git-hooks.sh} +0 -0
- package/pennyfarthing-dist/scripts/{release.sh → git/release.sh} +0 -0
- package/pennyfarthing-dist/scripts/{worktree-manager.sh → git/worktree-manager.sh} +0 -4
- package/pennyfarthing-dist/scripts/hooks/README.md +32 -0
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +3 -4
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +1 -2
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +2 -3
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +2 -3
- package/pennyfarthing-dist/scripts/jira/README.md +33 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +101 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +97 -0
- package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.mjs +327 -0
- package/pennyfarthing-dist/scripts/jira/jira-bidirectional-sync.test.mjs +503 -0
- package/pennyfarthing-dist/scripts/{utils → jira}/jira-claim-story.sh +8 -6
- package/pennyfarthing-dist/scripts/{utils/jira → jira}/jira-lib.mjs +10 -10
- package/pennyfarthing-dist/scripts/{utils → jira}/jira-lib.sh +18 -17
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +266 -0
- package/pennyfarthing-dist/scripts/{utils/jira → jira}/jira-sync-story.mjs +14 -14
- package/pennyfarthing-dist/scripts/{utils → jira}/jira-sync-story.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils/jira → jira}/jira-sync.mjs +4 -4
- package/pennyfarthing-dist/scripts/{utils → jira}/jira-sync.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +104 -0
- package/pennyfarthing-dist/scripts/{utils → jira}/sync-epic-to-jira.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/README.md +29 -0
- package/pennyfarthing-dist/scripts/{utils → lib}/background-tasks.sh +1 -1
- package/pennyfarthing-dist/scripts/{utils → lib}/checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → lib}/common.sh +1 -1
- package/pennyfarthing-dist/scripts/{utils → lib}/file-lock.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → lib}/find-root.sh +7 -5
- package/pennyfarthing-dist/scripts/{utils → lib}/logging.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → lib}/retry.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/README.md +44 -0
- package/pennyfarthing-dist/scripts/{add-short-names.mjs → misc/add-short-names.mjs} +0 -0
- package/pennyfarthing-dist/scripts/misc/backlog.sh +91 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/check-status.sh +4 -8
- package/pennyfarthing-dist/scripts/{deploy.sh → misc/deploy.sh} +0 -0
- package/pennyfarthing-dist/scripts/{doctor-dogfood.sh → misc/doctor-dogfood.sh} +2 -5
- package/pennyfarthing-dist/scripts/{utils → misc}/find-related-work.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/generate-skill-docs.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/log-skill-usage.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.mjs +474 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +9 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/repo-scan.sh +0 -0
- package/pennyfarthing-dist/scripts/{repo-utils.sh → misc/repo-utils.sh} +2 -2
- package/pennyfarthing-dist/scripts/{run-ci.sh → misc/run-ci.sh} +0 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/run-timestamp.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/session-cleanup.sh +0 -0
- package/pennyfarthing-dist/scripts/{utils → misc}/skill-usage-report.sh +0 -0
- package/pennyfarthing-dist/scripts/{statusline.sh → misc/statusline.sh} +2 -7
- package/pennyfarthing-dist/scripts/{uninstall.sh → misc/uninstall.sh} +4 -5
- package/pennyfarthing-dist/scripts/{utils → misc}/validate-subagent-frontmatter.sh +3 -3
- package/pennyfarthing-dist/scripts/sprint/README.md +29 -0
- package/pennyfarthing-dist/scripts/sprint/archive-story.sh +135 -0
- package/pennyfarthing-dist/scripts/sprint/available-stories.sh +97 -0
- package/pennyfarthing-dist/scripts/sprint/check-story.sh +164 -0
- package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +58 -0
- package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +69 -0
- package/pennyfarthing-dist/scripts/sprint/list-future.sh +151 -0
- package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +116 -0
- package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +164 -0
- package/pennyfarthing-dist/scripts/{utils → sprint}/sprint-common.sh +135 -0
- package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +39 -0
- package/pennyfarthing-dist/scripts/{utils → sprint}/sprint-metrics.sh +0 -0
- package/pennyfarthing-dist/scripts/sprint/sprint-status.sh +134 -0
- package/pennyfarthing-dist/scripts/story/README.md +23 -0
- package/pennyfarthing-dist/scripts/story/create-story.sh +159 -0
- package/pennyfarthing-dist/scripts/story/size-story.sh +198 -0
- package/pennyfarthing-dist/scripts/story/story-template.sh +162 -0
- package/pennyfarthing-dist/scripts/test/README.md +23 -0
- package/pennyfarthing-dist/scripts/{utils → test}/swebench-judge.py +0 -0
- package/pennyfarthing-dist/scripts/test/test-cache.sh +165 -0
- package/pennyfarthing-dist/scripts/{utils → test}/test-setup.sh +5 -5
- package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +515 -0
- package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +599 -0
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +332 -0
- package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +573 -0
- package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +859 -0
- package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +662 -0
- package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +589 -0
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +7 -9
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +18 -18
- package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +10 -10
- package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +1 -1
- package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +647 -0
- package/pennyfarthing-dist/scripts/theme/README.md +22 -0
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +203 -0
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +73 -0
- package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +97 -0
- package/pennyfarthing-dist/scripts/workflow/README.md +28 -0
- package/pennyfarthing-dist/scripts/{check.sh → workflow/check.sh} +0 -0
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +159 -0
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +228 -0
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +91 -0
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +163 -0
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +138 -0
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +256 -0
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +167 -0
- package/pennyfarthing-dist/skills/agentic-patterns/SKILL.md +6 -0
- package/pennyfarthing-dist/skills/changelog/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/code-review/SKILL.md +3 -3
- package/pennyfarthing-dist/skills/context-engineering/SKILL.md +6 -0
- package/pennyfarthing-dist/skills/cyclist/SKILL.md +56 -139
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +21 -5
- package/pennyfarthing-dist/skills/finalize-run/SKILL.md +1 -1
- package/pennyfarthing-dist/skills/jira/SKILL.md +381 -178
- package/pennyfarthing-dist/skills/judge/SKILL.md +76 -21
- package/pennyfarthing-dist/skills/just/SKILL.md +345 -102
- package/pennyfarthing-dist/skills/otel/skill.md +1 -0
- package/pennyfarthing-dist/skills/permissions/skill.md +1 -1
- package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +5 -0
- package/pennyfarthing-dist/skills/skill-registry.schema.json +5 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +22 -0
- package/pennyfarthing-dist/skills/sprint/scripts/archive-story.sh +101 -0
- package/pennyfarthing-dist/skills/sprint/scripts/available-stories.sh +97 -0
- package/pennyfarthing-dist/skills/sprint/scripts/check-story.sh +164 -0
- package/pennyfarthing-dist/skills/sprint/scripts/create-jira-epic.sh +101 -0
- package/pennyfarthing-dist/skills/sprint/scripts/new-sprint.sh +116 -0
- package/pennyfarthing-dist/skills/sprint/scripts/promote-epic.sh +164 -0
- package/pennyfarthing-dist/skills/sprint/scripts/sprint-info.sh +39 -0
- package/pennyfarthing-dist/skills/sprint/scripts/sprint-status.sh +147 -0
- package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +104 -0
- package/pennyfarthing-dist/skills/sprint/skill.md +465 -0
- package/pennyfarthing-dist/skills/story/scripts/create-story.sh +159 -0
- package/pennyfarthing-dist/skills/story/scripts/size-story.sh +198 -0
- package/pennyfarthing-dist/skills/story/scripts/story-template.sh +162 -0
- package/pennyfarthing-dist/skills/story/skill.md +219 -0
- package/pennyfarthing-dist/skills/systematic-debugging/SKILL.md +390 -0
- package/pennyfarthing-dist/skills/theme/skill.md +1 -1
- package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +91 -0
- package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +163 -0
- package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +138 -0
- package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +273 -0
- package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +167 -0
- package/pennyfarthing-dist/skills/workflow/skill.md +337 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +1 -1
- package/pennyfarthing-dist/workflows/agent-docs.yaml +1 -1
- package/pennyfarthing-dist/workflows/architecture/steps/step-01-initialize.md +101 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-01b-continue.md +93 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-02-context.md +115 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-03-patterns.md +133 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-04-components.md +138 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-05-interfaces.md +133 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-06-risks.md +142 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-07-document.md +160 -0
- package/pennyfarthing-dist/workflows/architecture/templates/architecture-decision.md +102 -0
- package/pennyfarthing-dist/workflows/architecture.yaml +65 -0
- package/pennyfarthing-dist/workflows/bdd.yaml +3 -1
- package/pennyfarthing-dist/workflows/brainstorming/brain-methods.csv +62 -0
- package/pennyfarthing-dist/workflows/brainstorming/checklist.md +44 -0
- package/pennyfarthing-dist/workflows/brainstorming/instructions.md +736 -0
- package/pennyfarthing-dist/workflows/brainstorming/workflow.yaml +49 -0
- package/pennyfarthing-dist/workflows/code-review/checklist.md +23 -0
- package/pennyfarthing-dist/workflows/code-review/instructions.md +234 -0
- package/pennyfarthing-dist/workflows/code-review/workflow.yaml +51 -0
- package/pennyfarthing-dist/workflows/dev-story/checklist.md +80 -0
- package/pennyfarthing-dist/workflows/dev-story/instructions.xml +410 -0
- package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +50 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-01-validate-prerequisites.md +256 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-02-design-epics.md +233 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-03-create-stories.md +272 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-04-final-validation.md +145 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/templates/epics-template.md +57 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/workflow.yaml +27 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-01-document-discovery.md +190 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-02-prd-analysis.md +178 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-03-epic-coverage-validation.md +179 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-04-ux-alignment.md +139 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-05-epic-quality-review.md +252 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-06-final-assessment.md +133 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/templates/readiness-report-template.md +4 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/workflow.yaml +40 -0
- package/pennyfarthing-dist/workflows/prd/data/domain-complexity.csv +13 -0
- package/pennyfarthing-dist/workflows/prd/data/prd-purpose.md +197 -0
- package/pennyfarthing-dist/workflows/prd/data/project-types.csv +11 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-01-init.md +191 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-01b-continue.md +153 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-02-discovery.md +224 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-03-success.md +226 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-04-journeys.md +213 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-05-domain.md +207 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-06-innovation.md +226 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-07-project-type.md +237 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-08-scoping.md +228 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-09-functional.md +231 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-10-nonfunctional.md +242 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-11-polish.md +217 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-12-complete.md +180 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-01-discovery.md +247 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-01b-legacy-conversion.md +208 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-02-review.md +249 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-03-edit.md +253 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-04-complete.md +168 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-01-discovery.md +218 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-02-format-detection.md +191 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-02b-parity-check.md +209 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-03-density-validation.md +174 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-04-brief-coverage-validation.md +214 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-05-measurability-validation.md +228 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-06-traceability-validation.md +217 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-07-implementation-leakage-validation.md +205 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-08-domain-compliance-validation.md +243 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-09-project-type-validation.md +263 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-10-smart-validation.md +209 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-11-holistic-quality-validation.md +264 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-12-completeness-validation.md +242 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-13-report-complete.md +232 -0
- package/pennyfarthing-dist/workflows/prd/templates/prd-template.md +10 -0
- package/pennyfarthing-dist/workflows/prd/workflow.yaml +42 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-01-init.md +177 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-01b-continue.md +161 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-02-vision.md +199 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-03-users.md +202 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-04-metrics.md +205 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-05-scope.md +219 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-06-complete.md +194 -0
- package/pennyfarthing-dist/workflows/product-brief/templates/product-brief.template.md +10 -0
- package/pennyfarthing-dist/workflows/product-brief/workflow.yaml +31 -0
- package/pennyfarthing-dist/workflows/project-context/project-context-template.md +21 -0
- package/pennyfarthing-dist/workflows/project-context/steps/step-01-discover.md +184 -0
- package/pennyfarthing-dist/workflows/project-context/steps/step-02-generate.md +318 -0
- package/pennyfarthing-dist/workflows/project-context/steps/step-03-complete.md +278 -0
- package/pennyfarthing-dist/workflows/project-context/workflow.yaml +27 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-01-mode-detection.md +156 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-02-context-gathering.md +120 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-03-execute.md +113 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-04-self-check.md +113 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-05-adversarial-review.md +106 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-06-resolve-findings.md +140 -0
- package/pennyfarthing-dist/workflows/quick-dev/workflow.yaml +27 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +189 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +144 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +128 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +191 -0
- package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +74 -0
- package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +27 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-01-init.md +137 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-02-domain-analysis.md +229 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-03-competitive-landscape.md +238 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-04-regulatory-focus.md +206 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-05-technical-trends.md +234 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-06-research-synthesis.md +443 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-01-init.md +182 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-02-customer-behavior.md +237 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-02-customer-insights.md +200 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-03-customer-pain-points.md +249 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-04-customer-decisions.md +259 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-05-competitive-analysis.md +177 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-06-research-completion.md +475 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-01-init.md +137 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-02-technical-overview.md +239 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-03-integration-patterns.md +248 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-04-architectural-patterns.md +202 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-05-implementation-research.md +239 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-06-research-synthesis.md +486 -0
- package/pennyfarthing-dist/workflows/research/templates/research.template.md +29 -0
- package/pennyfarthing-dist/workflows/research/workflow.yaml +45 -0
- package/pennyfarthing-dist/workflows/retrospective/checklist.md +31 -0
- package/pennyfarthing-dist/workflows/retrospective/instructions.md +1443 -0
- package/pennyfarthing-dist/workflows/retrospective/workflow.yaml +50 -0
- package/pennyfarthing-dist/workflows/sprint-planning/checklist.md +33 -0
- package/pennyfarthing-dist/workflows/sprint-planning/sprint-status-template.yaml +55 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-01-parse-epic-files.md +54 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-02-build-sprint-status.md +44 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-03-status-detection.md +64 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-04-generate-status-file.md +73 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-05-validate-and-report.md +56 -0
- package/pennyfarthing-dist/workflows/sprint-planning/workflow.yaml +34 -0
- package/pennyfarthing-dist/workflows/trivial.yaml +1 -1
- package/pennyfarthing-dist/workflows/ux-design/steps/step-01-init.md +135 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-01b-continue.md +127 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-02-discovery.md +190 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-03-core-experience.md +216 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-04-emotional-response.md +219 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-05-inspiration.md +234 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-06-design-system.md +252 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-07-defining-experience.md +254 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-08-visual-foundation.md +224 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-09-design-directions.md +224 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-10-user-journeys.md +241 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-11-component-strategy.md +248 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-12-ux-patterns.md +237 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-13-responsive-accessibility.md +264 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-14-complete.md +228 -0
- package/pennyfarthing-dist/workflows/ux-design/ux-design-template.md +13 -0
- package/pennyfarthing-dist/workflows/ux-design/workflow.yaml +41 -0
- package/packages/core/dist/scripts/generate-all-faces.d.ts +0 -10
- package/packages/core/dist/scripts/generate-all-faces.d.ts.map +0 -1
- package/packages/core/dist/scripts/generate-all-faces.js +0 -256
- package/packages/core/dist/scripts/generate-all-faces.js.map +0 -1
- package/packages/core/dist/scripts/generate-all-faces.test.d.ts +0 -17
- package/packages/core/dist/scripts/generate-all-faces.test.d.ts.map +0 -1
- package/packages/core/dist/scripts/generate-all-faces.test.js +0 -372
- package/packages/core/dist/scripts/generate-all-faces.test.js.map +0 -1
- package/packages/core/dist/scripts/generate-ascii-face.d.ts +0 -52
- package/packages/core/dist/scripts/generate-ascii-face.d.ts.map +0 -1
- package/packages/core/dist/scripts/generate-ascii-face.js +0 -155
- package/packages/core/dist/scripts/generate-ascii-face.js.map +0 -1
- package/packages/core/dist/scripts/generate-face.d.ts +0 -52
- package/packages/core/dist/scripts/generate-face.d.ts.map +0 -1
- package/packages/core/dist/scripts/generate-face.js +0 -199
- package/packages/core/dist/scripts/generate-face.js.map +0 -1
- package/packages/core/dist/scripts/generate-face.test.d.ts +0 -13
- package/packages/core/dist/scripts/generate-face.test.d.ts.map +0 -1
- package/packages/core/dist/scripts/generate-face.test.js +0 -301
- package/packages/core/dist/scripts/generate-face.test.js.map +0 -1
- package/pennyfarthing-dist/agents/generic-handoff.md +0 -454
- package/pennyfarthing-dist/agents/generic-sm-finish.md +0 -261
- package/pennyfarthing-dist/agents/generic-sm-setup.md +0 -214
- package/pennyfarthing-dist/commands/new-work.md +0 -127
- package/pennyfarthing-dist/guides/AGENT-SCOPES.md +0 -201
- package/pennyfarthing-dist/guides/persona-system.md +0 -294
- package/pennyfarthing-dist/guides/shared-agent-behavior.md +0 -388
- package/pennyfarthing-dist/guides/shared-context.md +0 -147
- package/pennyfarthing-dist/guides/strategic-agent-behavior.md +0 -348
- package/pennyfarthing-dist/guides/tactical-agent-behavior.md +0 -1041
- package/pennyfarthing-dist/scripts/prime.sh +0 -161
- package/pennyfarthing-dist/scripts/run.sh +0 -65
- package/pennyfarthing-dist/skills/sprint-context/SKILL.md +0 -120
- package/pennyfarthing-dist/skills/story-management/SKILL.md +0 -208
- package/pennyfarthing-dist/skills/workflow/SKILL.md +0 -160
- /package/pennyfarthing-dist/commands/{brainstorm.md → brainstorming.md} +0 -0
- /package/pennyfarthing-dist/scripts/{utils → test}/ground-truth-judge.py +0 -0
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Story 47-2: Sync sprint numbers with Jira sprint IDs
|
|
3
|
+
*
|
|
4
|
+
* These tests define the contract for syncing Pennyfarthing sprint numbers
|
|
5
|
+
* with Jira sprint IDs, enabling bidirectional sprint tracking.
|
|
6
|
+
*
|
|
7
|
+
* Acceptance Criteria:
|
|
8
|
+
* 1. Sprint YAML references Jira sprint ID (e.g., 275)
|
|
9
|
+
* 2. Status check queries Jira sprint for membership
|
|
10
|
+
* 3. Sprint velocity pulls from Jira sprint metrics
|
|
11
|
+
* 4. Local sprint number matches Jira sprint
|
|
12
|
+
*
|
|
13
|
+
* Run with: npm test
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
16
|
+
import assert from 'node:assert';
|
|
17
|
+
import { mkdirSync, rmSync, existsSync, writeFileSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
// Get directory for test fixtures
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const TEST_DIR = join(__dirname, '__test_jira_sprint_sync__');
|
|
23
|
+
// Import the sprint sync functions (to be implemented)
|
|
24
|
+
import { getJiraSprintInfo, getSprintIssues, isStoryInJiraSprint, getSprintVelocityFromJira, validateSprintAlignment, addJiraSprintIdToYaml,
|
|
25
|
+
// Story 47-3 imports
|
|
26
|
+
getYamlStoryIds, findJiraOnlyStories, formatMissingStoriesReport, importMissingStoriesToYaml } from './jira-sprint-sync.js';
|
|
27
|
+
describe('Jira Sprint Sync (47-2)', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
if (existsSync(TEST_DIR)) {
|
|
30
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
if (existsSync(TEST_DIR)) {
|
|
36
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// ============================================
|
|
40
|
+
// AC1: Sprint YAML references Jira sprint ID
|
|
41
|
+
// ============================================
|
|
42
|
+
describe('addJiraSprintIdToYaml() - AC1: Sprint YAML references Jira sprint ID', () => {
|
|
43
|
+
it('should add jira_sprint_id field to sprint section', async () => {
|
|
44
|
+
const sprintYaml = `sprint:
|
|
45
|
+
number: 11
|
|
46
|
+
goal: Complete Epic 31 workflow engine
|
|
47
|
+
planned_start: 2026-01-15
|
|
48
|
+
status: active
|
|
49
|
+
velocity_target: 22
|
|
50
|
+
`;
|
|
51
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
52
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
53
|
+
const result = await addJiraSprintIdToYaml({
|
|
54
|
+
sprintPath,
|
|
55
|
+
jiraSprintId: 275
|
|
56
|
+
});
|
|
57
|
+
assert.strictEqual(result.success, true, 'Update should succeed');
|
|
58
|
+
const updatedContent = readFileSync(sprintPath, 'utf-8');
|
|
59
|
+
assert.ok(updatedContent.includes('jira_sprint_id: 275'), 'YAML should contain jira_sprint_id field');
|
|
60
|
+
});
|
|
61
|
+
it('should preserve existing sprint fields when adding jira_sprint_id', async () => {
|
|
62
|
+
const sprintYaml = `sprint:
|
|
63
|
+
number: 11
|
|
64
|
+
goal: Complete Epic 31 workflow engine
|
|
65
|
+
planned_start: 2026-01-15
|
|
66
|
+
status: active
|
|
67
|
+
velocity_target: 22
|
|
68
|
+
`;
|
|
69
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
70
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
71
|
+
await addJiraSprintIdToYaml({
|
|
72
|
+
sprintPath,
|
|
73
|
+
jiraSprintId: 275
|
|
74
|
+
});
|
|
75
|
+
const updatedContent = readFileSync(sprintPath, 'utf-8');
|
|
76
|
+
assert.ok(updatedContent.includes('number: 11'), 'Number should be preserved');
|
|
77
|
+
assert.ok(updatedContent.includes('goal: Complete Epic 31'), 'Goal should be preserved');
|
|
78
|
+
assert.ok(updatedContent.includes('velocity_target: 22'), 'Velocity target should be preserved');
|
|
79
|
+
});
|
|
80
|
+
it('should update existing jira_sprint_id if already present', async () => {
|
|
81
|
+
const sprintYaml = `sprint:
|
|
82
|
+
number: 11
|
|
83
|
+
jira_sprint_id: 274
|
|
84
|
+
goal: Old sprint
|
|
85
|
+
status: active
|
|
86
|
+
`;
|
|
87
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
88
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
89
|
+
const result = await addJiraSprintIdToYaml({
|
|
90
|
+
sprintPath,
|
|
91
|
+
jiraSprintId: 275,
|
|
92
|
+
force: true
|
|
93
|
+
});
|
|
94
|
+
assert.strictEqual(result.success, true);
|
|
95
|
+
const updatedContent = readFileSync(sprintPath, 'utf-8');
|
|
96
|
+
assert.ok(updatedContent.includes('jira_sprint_id: 275'), 'Should have updated jira_sprint_id');
|
|
97
|
+
assert.ok(!updatedContent.includes('jira_sprint_id: 274'), 'Should not have old jira_sprint_id');
|
|
98
|
+
});
|
|
99
|
+
it('should fail if jira_sprint_id exists and force is false', async () => {
|
|
100
|
+
const sprintYaml = `sprint:
|
|
101
|
+
number: 11
|
|
102
|
+
jira_sprint_id: 274
|
|
103
|
+
status: active
|
|
104
|
+
`;
|
|
105
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
106
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
107
|
+
const result = await addJiraSprintIdToYaml({
|
|
108
|
+
sprintPath,
|
|
109
|
+
jiraSprintId: 275,
|
|
110
|
+
force: false
|
|
111
|
+
});
|
|
112
|
+
assert.strictEqual(result.success, false);
|
|
113
|
+
assert.ok(result.error?.includes('already has'), 'Should mention existing ID');
|
|
114
|
+
});
|
|
115
|
+
it('should handle file not found gracefully', async () => {
|
|
116
|
+
const result = await addJiraSprintIdToYaml({
|
|
117
|
+
sprintPath: join(TEST_DIR, 'nonexistent.yaml'),
|
|
118
|
+
jiraSprintId: 275
|
|
119
|
+
});
|
|
120
|
+
assert.strictEqual(result.success, false);
|
|
121
|
+
assert.ok(result.error?.includes('not found') || result.error?.includes('ENOENT'), 'Should report file not found');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// ============================================
|
|
125
|
+
// AC2: Status check queries Jira sprint for membership
|
|
126
|
+
// ============================================
|
|
127
|
+
describe('getJiraSprintInfo() - AC2: Query Jira sprint for membership', () => {
|
|
128
|
+
it('should return sprint details from Jira', async () => {
|
|
129
|
+
const result = await getJiraSprintInfo({
|
|
130
|
+
sprintId: 275,
|
|
131
|
+
_mockResponse: {
|
|
132
|
+
id: 275,
|
|
133
|
+
name: 'Sprint 11',
|
|
134
|
+
state: 'active',
|
|
135
|
+
startDate: '2026-01-15',
|
|
136
|
+
endDate: '2026-01-29'
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
assert.strictEqual(result.success, true);
|
|
140
|
+
assert.strictEqual(result.sprint?.id, 275);
|
|
141
|
+
assert.strictEqual(result.sprint?.name, 'Sprint 11');
|
|
142
|
+
assert.strictEqual(result.sprint?.state, 'active');
|
|
143
|
+
});
|
|
144
|
+
it('should return null for non-existent sprint', async () => {
|
|
145
|
+
const result = await getJiraSprintInfo({
|
|
146
|
+
sprintId: 99999,
|
|
147
|
+
_mockResponse: null
|
|
148
|
+
});
|
|
149
|
+
assert.strictEqual(result.success, false);
|
|
150
|
+
assert.ok(result.error?.includes('not found'), 'Should indicate sprint not found');
|
|
151
|
+
});
|
|
152
|
+
it('should handle Jira API errors', async () => {
|
|
153
|
+
const result = await getJiraSprintInfo({
|
|
154
|
+
sprintId: 275,
|
|
155
|
+
_mockError: 'Connection refused'
|
|
156
|
+
});
|
|
157
|
+
assert.strictEqual(result.success, false);
|
|
158
|
+
assert.ok(result.error, 'Should have error message');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('getSprintIssues() - AC2: List issues in Jira sprint', () => {
|
|
162
|
+
it('should return issues in the sprint', async () => {
|
|
163
|
+
const result = await getSprintIssues({
|
|
164
|
+
sprintId: 275,
|
|
165
|
+
_mockResponse: [
|
|
166
|
+
{ key: 'MSSCI-11798', summary: 'Sprint sync story', status: 'In Progress' },
|
|
167
|
+
{ key: 'MSSCI-11750', summary: 'Another story', status: 'Done' }
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
assert.strictEqual(result.success, true);
|
|
171
|
+
assert.strictEqual(result.issues?.length, 2);
|
|
172
|
+
assert.strictEqual(result.issues?.[0].key, 'MSSCI-11798');
|
|
173
|
+
});
|
|
174
|
+
it('should filter by label when provided', async () => {
|
|
175
|
+
const result = await getSprintIssues({
|
|
176
|
+
sprintId: 275,
|
|
177
|
+
label: 'pennyfarthing',
|
|
178
|
+
_mockResponse: [
|
|
179
|
+
{ key: 'MSSCI-11798', summary: 'Pennyfarthing story', status: 'In Progress', labels: ['pennyfarthing'] }
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
assert.strictEqual(result.success, true);
|
|
183
|
+
assert.strictEqual(result.issues?.length, 1);
|
|
184
|
+
});
|
|
185
|
+
it('should return empty array for sprint with no issues', async () => {
|
|
186
|
+
const result = await getSprintIssues({
|
|
187
|
+
sprintId: 275,
|
|
188
|
+
_mockResponse: []
|
|
189
|
+
});
|
|
190
|
+
assert.strictEqual(result.success, true);
|
|
191
|
+
assert.strictEqual(result.issues?.length, 0);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('isStoryInJiraSprint() - AC2: Check sprint membership', () => {
|
|
195
|
+
it('should return true if story is in the sprint', async () => {
|
|
196
|
+
const result = await isStoryInJiraSprint({
|
|
197
|
+
jiraKey: 'MSSCI-11798',
|
|
198
|
+
sprintId: 275,
|
|
199
|
+
_mockResponse: true
|
|
200
|
+
});
|
|
201
|
+
assert.strictEqual(result.success, true);
|
|
202
|
+
assert.strictEqual(result.inSprint, true);
|
|
203
|
+
});
|
|
204
|
+
it('should return false if story is not in the sprint', async () => {
|
|
205
|
+
const result = await isStoryInJiraSprint({
|
|
206
|
+
jiraKey: 'MSSCI-11798',
|
|
207
|
+
sprintId: 274, // Different sprint
|
|
208
|
+
_mockResponse: false
|
|
209
|
+
});
|
|
210
|
+
assert.strictEqual(result.success, true);
|
|
211
|
+
assert.strictEqual(result.inSprint, false);
|
|
212
|
+
});
|
|
213
|
+
it('should handle story not found in Jira', async () => {
|
|
214
|
+
const result = await isStoryInJiraSprint({
|
|
215
|
+
jiraKey: 'MSSCI-99999',
|
|
216
|
+
sprintId: 275,
|
|
217
|
+
_mockError: 'Issue not found'
|
|
218
|
+
});
|
|
219
|
+
assert.strictEqual(result.success, false);
|
|
220
|
+
assert.ok(result.error?.includes('not found'));
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
// ============================================
|
|
224
|
+
// AC3: Sprint velocity pulls from Jira sprint metrics
|
|
225
|
+
// ============================================
|
|
226
|
+
describe('getSprintVelocityFromJira() - AC3: Sprint velocity from Jira', () => {
|
|
227
|
+
it('should return velocity metrics from Jira sprint', async () => {
|
|
228
|
+
const result = await getSprintVelocityFromJira({
|
|
229
|
+
sprintId: 275,
|
|
230
|
+
_mockResponse: {
|
|
231
|
+
totalPoints: 22,
|
|
232
|
+
completedPoints: 15,
|
|
233
|
+
remainingPoints: 7,
|
|
234
|
+
issueCount: 8,
|
|
235
|
+
completedCount: 5
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
assert.strictEqual(result.success, true);
|
|
239
|
+
assert.strictEqual(result.metrics?.totalPoints, 22);
|
|
240
|
+
assert.strictEqual(result.metrics?.completedPoints, 15);
|
|
241
|
+
assert.strictEqual(result.metrics?.remainingPoints, 7);
|
|
242
|
+
});
|
|
243
|
+
it('should calculate velocity percentage', async () => {
|
|
244
|
+
const result = await getSprintVelocityFromJira({
|
|
245
|
+
sprintId: 275,
|
|
246
|
+
_mockResponse: {
|
|
247
|
+
totalPoints: 20,
|
|
248
|
+
completedPoints: 10,
|
|
249
|
+
remainingPoints: 10
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
assert.strictEqual(result.success, true);
|
|
253
|
+
assert.strictEqual(result.metrics?.velocityPercentage, 50);
|
|
254
|
+
});
|
|
255
|
+
it('should handle sprint with no points', async () => {
|
|
256
|
+
const result = await getSprintVelocityFromJira({
|
|
257
|
+
sprintId: 275,
|
|
258
|
+
_mockResponse: {
|
|
259
|
+
totalPoints: 0,
|
|
260
|
+
completedPoints: 0,
|
|
261
|
+
remainingPoints: 0
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
assert.strictEqual(result.success, true);
|
|
265
|
+
assert.strictEqual(result.metrics?.totalPoints, 0);
|
|
266
|
+
// Should not divide by zero
|
|
267
|
+
assert.ok(result.metrics?.velocityPercentage === 0 || result.metrics?.velocityPercentage === null, 'Should handle zero total gracefully');
|
|
268
|
+
});
|
|
269
|
+
it('should return issue breakdown by status', async () => {
|
|
270
|
+
const result = await getSprintVelocityFromJira({
|
|
271
|
+
sprintId: 275,
|
|
272
|
+
_mockResponse: {
|
|
273
|
+
totalPoints: 22,
|
|
274
|
+
completedPoints: 15,
|
|
275
|
+
remainingPoints: 7,
|
|
276
|
+
byStatus: {
|
|
277
|
+
'To Do': { count: 2, points: 3 },
|
|
278
|
+
'In Progress': { count: 1, points: 4 },
|
|
279
|
+
'Done': { count: 5, points: 15 }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
assert.strictEqual(result.success, true);
|
|
284
|
+
assert.ok(result.metrics?.byStatus, 'Should have status breakdown');
|
|
285
|
+
assert.strictEqual(result.metrics?.byStatus?.Done?.points, 15);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
// ============================================
|
|
289
|
+
// AC4: Local sprint name matches Jira sprint
|
|
290
|
+
// ============================================
|
|
291
|
+
describe('validateSprintAlignment() - AC4: Local sprint matches Jira', () => {
|
|
292
|
+
it('should return aligned when sprint names match', async () => {
|
|
293
|
+
const sprintYaml = `sprint:
|
|
294
|
+
name: "TO Sprint 2604"
|
|
295
|
+
jira_sprint_id: 275
|
|
296
|
+
jira_sprint_name: "TO Sprint 2604"
|
|
297
|
+
status: active
|
|
298
|
+
`;
|
|
299
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
300
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
301
|
+
const result = await validateSprintAlignment({
|
|
302
|
+
sprintPath,
|
|
303
|
+
_mockJiraSprint: {
|
|
304
|
+
id: 275,
|
|
305
|
+
name: 'TO Sprint 2604',
|
|
306
|
+
state: 'active'
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
assert.strictEqual(result.success, true);
|
|
310
|
+
assert.strictEqual(result.aligned, true);
|
|
311
|
+
assert.strictEqual(result.localSprintName, 'TO Sprint 2604');
|
|
312
|
+
assert.strictEqual(result.jiraSprintId, 275);
|
|
313
|
+
});
|
|
314
|
+
it('should detect misalignment when sprint names differ', async () => {
|
|
315
|
+
const sprintYaml = `sprint:
|
|
316
|
+
name: "TO Sprint 2604"
|
|
317
|
+
jira_sprint_id: 275
|
|
318
|
+
jira_sprint_name: "TO Sprint 2604"
|
|
319
|
+
status: active
|
|
320
|
+
`;
|
|
321
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
322
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
323
|
+
const result = await validateSprintAlignment({
|
|
324
|
+
sprintPath,
|
|
325
|
+
_mockJiraSprint: {
|
|
326
|
+
id: 275,
|
|
327
|
+
name: 'TO Sprint 2605', // Mismatch!
|
|
328
|
+
state: 'active'
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
assert.strictEqual(result.success, true);
|
|
332
|
+
assert.strictEqual(result.aligned, false);
|
|
333
|
+
assert.ok(result.warning?.includes('mismatch'), 'Should warn about mismatch');
|
|
334
|
+
});
|
|
335
|
+
it('should detect when Jira sprint is closed but local is active', async () => {
|
|
336
|
+
const sprintYaml = `sprint:
|
|
337
|
+
name: "TO Sprint 2604"
|
|
338
|
+
jira_sprint_id: 275
|
|
339
|
+
jira_sprint_name: "TO Sprint 2604"
|
|
340
|
+
status: active
|
|
341
|
+
`;
|
|
342
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
343
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
344
|
+
const result = await validateSprintAlignment({
|
|
345
|
+
sprintPath,
|
|
346
|
+
_mockJiraSprint: {
|
|
347
|
+
id: 275,
|
|
348
|
+
name: 'TO Sprint 2604',
|
|
349
|
+
state: 'closed' // Jira sprint closed!
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
assert.strictEqual(result.success, true);
|
|
353
|
+
assert.strictEqual(result.aligned, false);
|
|
354
|
+
assert.ok(result.warning?.includes('closed'), 'Should warn about closed sprint');
|
|
355
|
+
});
|
|
356
|
+
it('should handle missing jira_sprint_id in YAML', async () => {
|
|
357
|
+
const sprintYaml = `sprint:
|
|
358
|
+
name: "TO Sprint 2604"
|
|
359
|
+
status: active
|
|
360
|
+
`;
|
|
361
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
362
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
363
|
+
const result = await validateSprintAlignment({
|
|
364
|
+
sprintPath,
|
|
365
|
+
_mockJiraSprint: null
|
|
366
|
+
});
|
|
367
|
+
assert.strictEqual(result.success, false);
|
|
368
|
+
assert.ok(result.error?.includes('jira_sprint_id') || result.error?.includes('not configured'), 'Should report missing jira_sprint_id');
|
|
369
|
+
});
|
|
370
|
+
it('should use jira_sprint_name for comparison if present', async () => {
|
|
371
|
+
const sprintYaml = `sprint:
|
|
372
|
+
name: "My Local Name"
|
|
373
|
+
jira_sprint_id: 275
|
|
374
|
+
jira_sprint_name: "TO Sprint 2604"
|
|
375
|
+
status: active
|
|
376
|
+
`;
|
|
377
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
378
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
379
|
+
const result = await validateSprintAlignment({
|
|
380
|
+
sprintPath,
|
|
381
|
+
_mockJiraSprint: {
|
|
382
|
+
id: 275,
|
|
383
|
+
name: 'TO Sprint 2604',
|
|
384
|
+
state: 'active'
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
assert.strictEqual(result.success, true);
|
|
388
|
+
assert.strictEqual(result.aligned, true);
|
|
389
|
+
assert.strictEqual(result.jiraSprintName, 'TO Sprint 2604');
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
// ============================================
|
|
393
|
+
// Integration tests
|
|
394
|
+
// ============================================
|
|
395
|
+
describe('Integration: Full sprint sync workflow', () => {
|
|
396
|
+
it('should sync sprint with Jira and update YAML', async () => {
|
|
397
|
+
// Start with no jira_sprint_id
|
|
398
|
+
const sprintYaml = `sprint:
|
|
399
|
+
name: "TO Sprint 2604"
|
|
400
|
+
goal: Test sprint
|
|
401
|
+
status: active
|
|
402
|
+
`;
|
|
403
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
404
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
405
|
+
// Step 1: Add jira_sprint_id
|
|
406
|
+
const addResult = await addJiraSprintIdToYaml({
|
|
407
|
+
sprintPath,
|
|
408
|
+
jiraSprintId: 275
|
|
409
|
+
});
|
|
410
|
+
assert.strictEqual(addResult.success, true);
|
|
411
|
+
// Step 2: Validate alignment
|
|
412
|
+
const validateResult = await validateSprintAlignment({
|
|
413
|
+
sprintPath,
|
|
414
|
+
_mockJiraSprint: {
|
|
415
|
+
id: 275,
|
|
416
|
+
name: 'TO Sprint 2604',
|
|
417
|
+
state: 'active'
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
assert.strictEqual(validateResult.success, true);
|
|
421
|
+
assert.strictEqual(validateResult.aligned, true);
|
|
422
|
+
// Step 3: Get velocity
|
|
423
|
+
const velocityResult = await getSprintVelocityFromJira({
|
|
424
|
+
sprintId: 275,
|
|
425
|
+
_mockResponse: {
|
|
426
|
+
totalPoints: 22,
|
|
427
|
+
completedPoints: 10,
|
|
428
|
+
remainingPoints: 12
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
assert.strictEqual(velocityResult.success, true);
|
|
432
|
+
assert.strictEqual(velocityResult.metrics?.totalPoints, 22);
|
|
433
|
+
});
|
|
434
|
+
it('should warn when story is not in Jira sprint', async () => {
|
|
435
|
+
// This tests the SM setup flow integration
|
|
436
|
+
const membershipResult = await isStoryInJiraSprint({
|
|
437
|
+
jiraKey: 'MSSCI-11798',
|
|
438
|
+
sprintId: 275,
|
|
439
|
+
_mockResponse: false
|
|
440
|
+
});
|
|
441
|
+
assert.strictEqual(membershipResult.success, true);
|
|
442
|
+
assert.strictEqual(membershipResult.inSprint, false);
|
|
443
|
+
// In real implementation, this would trigger a warning in SM setup
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
// ============================================
|
|
448
|
+
// Story 47-3: Detect Jira-only stories missing from sprint YAML
|
|
449
|
+
// ============================================
|
|
450
|
+
//
|
|
451
|
+
// Acceptance Criteria:
|
|
452
|
+
// 1. Sync script queries Jira sprint for all pennyfarthing stories
|
|
453
|
+
// 2. Compares against sprint YAML story list
|
|
454
|
+
// 3. Reports stories in Jira but not in YAML
|
|
455
|
+
// 4. Optionally imports missing stories to YAML
|
|
456
|
+
describe('Jira-Only Story Detection (47-3)', () => {
|
|
457
|
+
beforeEach(() => {
|
|
458
|
+
if (existsSync(TEST_DIR)) {
|
|
459
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
460
|
+
}
|
|
461
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
462
|
+
});
|
|
463
|
+
afterEach(() => {
|
|
464
|
+
if (existsSync(TEST_DIR)) {
|
|
465
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
// ============================================
|
|
469
|
+
// AC1 + AC2: Query Jira and compare against YAML
|
|
470
|
+
// ============================================
|
|
471
|
+
describe('getYamlStoryIds() - Extract story IDs from sprint YAML', () => {
|
|
472
|
+
it('should extract all story IDs from sprint YAML', async () => {
|
|
473
|
+
const sprintYaml = `sprint:
|
|
474
|
+
number: 11
|
|
475
|
+
status: active
|
|
476
|
+
epics:
|
|
477
|
+
- id: epic-47
|
|
478
|
+
title: Jira Sync
|
|
479
|
+
stories:
|
|
480
|
+
- id: "47-1"
|
|
481
|
+
title: Auto-create Jira epic
|
|
482
|
+
status: done
|
|
483
|
+
- id: "47-2"
|
|
484
|
+
title: Sync sprint numbers
|
|
485
|
+
status: done
|
|
486
|
+
- id: "47-3"
|
|
487
|
+
title: Detect Jira-only stories
|
|
488
|
+
status: in_progress
|
|
489
|
+
`;
|
|
490
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
491
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
492
|
+
const result = await getYamlStoryIds({ sprintPath });
|
|
493
|
+
assert.strictEqual(result.success, true);
|
|
494
|
+
assert.ok(result.storyIds, 'Should return storyIds array');
|
|
495
|
+
assert.strictEqual(result.storyIds?.length, 3);
|
|
496
|
+
assert.ok(result.storyIds?.includes('47-1'));
|
|
497
|
+
assert.ok(result.storyIds?.includes('47-2'));
|
|
498
|
+
assert.ok(result.storyIds?.includes('47-3'));
|
|
499
|
+
});
|
|
500
|
+
it('should extract stories from multiple epics', async () => {
|
|
501
|
+
const sprintYaml = `sprint:
|
|
502
|
+
number: 11
|
|
503
|
+
status: active
|
|
504
|
+
epics:
|
|
505
|
+
- id: epic-31
|
|
506
|
+
title: Workflow Engine
|
|
507
|
+
stories:
|
|
508
|
+
- id: "31-1"
|
|
509
|
+
status: done
|
|
510
|
+
- id: "31-2"
|
|
511
|
+
status: done
|
|
512
|
+
- id: epic-47
|
|
513
|
+
title: Jira Sync
|
|
514
|
+
stories:
|
|
515
|
+
- id: "47-1"
|
|
516
|
+
status: done
|
|
517
|
+
`;
|
|
518
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
519
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
520
|
+
const result = await getYamlStoryIds({ sprintPath });
|
|
521
|
+
assert.strictEqual(result.success, true);
|
|
522
|
+
assert.strictEqual(result.storyIds?.length, 3);
|
|
523
|
+
assert.ok(result.storyIds?.includes('31-1'));
|
|
524
|
+
assert.ok(result.storyIds?.includes('31-2'));
|
|
525
|
+
assert.ok(result.storyIds?.includes('47-1'));
|
|
526
|
+
});
|
|
527
|
+
it('should return empty array for YAML with no stories', async () => {
|
|
528
|
+
const sprintYaml = `sprint:
|
|
529
|
+
number: 11
|
|
530
|
+
status: active
|
|
531
|
+
epics: []
|
|
532
|
+
`;
|
|
533
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
534
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
535
|
+
const result = await getYamlStoryIds({ sprintPath });
|
|
536
|
+
assert.strictEqual(result.success, true);
|
|
537
|
+
assert.strictEqual(result.storyIds?.length, 0);
|
|
538
|
+
});
|
|
539
|
+
it('should handle missing file gracefully', async () => {
|
|
540
|
+
const result = await getYamlStoryIds({
|
|
541
|
+
sprintPath: join(TEST_DIR, 'nonexistent.yaml')
|
|
542
|
+
});
|
|
543
|
+
assert.strictEqual(result.success, false);
|
|
544
|
+
assert.ok(result.error?.includes('not found') || result.error?.includes('ENOENT'));
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
describe('findJiraOnlyStories() - Compare Jira issues with YAML stories', () => {
|
|
548
|
+
it('should find stories in Jira but not in YAML', async () => {
|
|
549
|
+
const jiraIssues = [
|
|
550
|
+
{ key: 'MSSCI-11797', summary: '47-1: Auto-create Jira epic', status: 'Done' },
|
|
551
|
+
{ key: 'MSSCI-11798', summary: '47-2: Sync sprint numbers', status: 'Done' },
|
|
552
|
+
{ key: 'MSSCI-11800', summary: '47-4: Bidirectional sync', status: 'To Do' } // Not in YAML!
|
|
553
|
+
];
|
|
554
|
+
const yamlStoryIds = ['47-1', '47-2', '47-3'];
|
|
555
|
+
const result = await findJiraOnlyStories({
|
|
556
|
+
jiraIssues,
|
|
557
|
+
yamlStoryIds
|
|
558
|
+
});
|
|
559
|
+
assert.strictEqual(result.success, true);
|
|
560
|
+
assert.strictEqual(result.missingStories?.length, 1);
|
|
561
|
+
assert.strictEqual(result.missingStories?.[0].key, 'MSSCI-11800');
|
|
562
|
+
assert.strictEqual(result.missingStories?.[0].storyId, '47-4');
|
|
563
|
+
});
|
|
564
|
+
it('should return empty when all Jira stories are in YAML', async () => {
|
|
565
|
+
const jiraIssues = [
|
|
566
|
+
{ key: 'MSSCI-11797', summary: '47-1: Auto-create Jira epic', status: 'Done' },
|
|
567
|
+
{ key: 'MSSCI-11798', summary: '47-2: Sync sprint numbers', status: 'Done' }
|
|
568
|
+
];
|
|
569
|
+
const yamlStoryIds = ['47-1', '47-2', '47-3'];
|
|
570
|
+
const result = await findJiraOnlyStories({
|
|
571
|
+
jiraIssues,
|
|
572
|
+
yamlStoryIds
|
|
573
|
+
});
|
|
574
|
+
assert.strictEqual(result.success, true);
|
|
575
|
+
assert.strictEqual(result.missingStories?.length, 0);
|
|
576
|
+
});
|
|
577
|
+
it('should extract story ID from Jira summary format', async () => {
|
|
578
|
+
// Jira summaries often have format "47-1: Title" or "Story 47-1: Title"
|
|
579
|
+
const jiraIssues = [
|
|
580
|
+
{ key: 'MSSCI-100', summary: 'Story 31-5: New feature', status: 'In Progress' },
|
|
581
|
+
{ key: 'MSSCI-101', summary: '31-6: Another feature', status: 'To Do' }
|
|
582
|
+
];
|
|
583
|
+
const yamlStoryIds = ['31-1', '31-2'];
|
|
584
|
+
const result = await findJiraOnlyStories({
|
|
585
|
+
jiraIssues,
|
|
586
|
+
yamlStoryIds
|
|
587
|
+
});
|
|
588
|
+
assert.strictEqual(result.success, true);
|
|
589
|
+
assert.strictEqual(result.missingStories?.length, 2);
|
|
590
|
+
assert.strictEqual(result.missingStories?.[0].storyId, '31-5');
|
|
591
|
+
assert.strictEqual(result.missingStories?.[1].storyId, '31-6');
|
|
592
|
+
});
|
|
593
|
+
it('should filter by pennyfarthing label when provided', async () => {
|
|
594
|
+
const jiraIssues = [
|
|
595
|
+
{ key: 'MSSCI-100', summary: '47-4: PF story', status: 'To Do', labels: ['pennyfarthing'] },
|
|
596
|
+
{ key: 'MSSCI-101', summary: 'OTHER-1: Not PF', status: 'To Do', labels: ['other-project'] }
|
|
597
|
+
];
|
|
598
|
+
const yamlStoryIds = ['47-1'];
|
|
599
|
+
const result = await findJiraOnlyStories({
|
|
600
|
+
jiraIssues,
|
|
601
|
+
yamlStoryIds,
|
|
602
|
+
filterLabel: 'pennyfarthing'
|
|
603
|
+
});
|
|
604
|
+
assert.strictEqual(result.success, true);
|
|
605
|
+
assert.strictEqual(result.missingStories?.length, 1);
|
|
606
|
+
assert.strictEqual(result.missingStories?.[0].key, 'MSSCI-100');
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
// ============================================
|
|
610
|
+
// AC3: Report stories in Jira but not in YAML
|
|
611
|
+
// ============================================
|
|
612
|
+
describe('formatMissingStoriesReport() - Human-readable report', () => {
|
|
613
|
+
it('should format missing stories as readable report', async () => {
|
|
614
|
+
const missingStories = [
|
|
615
|
+
{ key: 'MSSCI-11800', summary: '47-4: Bidirectional sync', status: 'To Do', storyId: '47-4' },
|
|
616
|
+
{ key: 'MSSCI-11801', summary: '47-5: Retrofit epics', status: 'To Do', storyId: '47-5' }
|
|
617
|
+
];
|
|
618
|
+
const result = await formatMissingStoriesReport({ missingStories });
|
|
619
|
+
assert.strictEqual(result.success, true);
|
|
620
|
+
assert.ok(result.report, 'Should have report string');
|
|
621
|
+
assert.ok(result.report?.includes('MSSCI-11800'));
|
|
622
|
+
assert.ok(result.report?.includes('47-4'));
|
|
623
|
+
assert.ok(result.report?.includes('Bidirectional sync'));
|
|
624
|
+
assert.ok(result.report?.includes('2 stories')); // Count in header
|
|
625
|
+
});
|
|
626
|
+
it('should return "no missing stories" message when empty', async () => {
|
|
627
|
+
const result = await formatMissingStoriesReport({ missingStories: [] });
|
|
628
|
+
assert.strictEqual(result.success, true);
|
|
629
|
+
assert.ok(result.report?.toLowerCase().includes('no missing') ||
|
|
630
|
+
result.report?.toLowerCase().includes('all synced') ||
|
|
631
|
+
result.report?.includes('0 stories'));
|
|
632
|
+
});
|
|
633
|
+
it('should include Jira URL in report', async () => {
|
|
634
|
+
const missingStories = [
|
|
635
|
+
{ key: 'MSSCI-11800', summary: '47-4: Test', status: 'To Do', storyId: '47-4' }
|
|
636
|
+
];
|
|
637
|
+
const result = await formatMissingStoriesReport({
|
|
638
|
+
missingStories,
|
|
639
|
+
jiraBaseUrl: 'https://1898andco.atlassian.net'
|
|
640
|
+
});
|
|
641
|
+
assert.strictEqual(result.success, true);
|
|
642
|
+
assert.ok(result.report?.includes('https://1898andco.atlassian.net/browse/MSSCI-11800'));
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
// ============================================
|
|
646
|
+
// AC4: Optionally import missing stories to YAML
|
|
647
|
+
// ============================================
|
|
648
|
+
describe('importMissingStoriesToYaml() - Add missing stories to sprint YAML', () => {
|
|
649
|
+
it('should add missing story to existing epic in YAML', async () => {
|
|
650
|
+
const sprintYaml = `sprint:
|
|
651
|
+
number: 11
|
|
652
|
+
status: active
|
|
653
|
+
epics:
|
|
654
|
+
- id: epic-47
|
|
655
|
+
title: Jira Sync
|
|
656
|
+
stories:
|
|
657
|
+
- id: "47-1"
|
|
658
|
+
title: Auto-create Jira epic
|
|
659
|
+
status: done
|
|
660
|
+
`;
|
|
661
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
662
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
663
|
+
const missingStories = [
|
|
664
|
+
{ key: 'MSSCI-11800', summary: '47-4: Bidirectional sync', status: 'To Do', storyId: '47-4', points: 4 }
|
|
665
|
+
];
|
|
666
|
+
const result = await importMissingStoriesToYaml({
|
|
667
|
+
sprintPath,
|
|
668
|
+
missingStories,
|
|
669
|
+
targetEpicId: 'epic-47'
|
|
670
|
+
});
|
|
671
|
+
assert.strictEqual(result.success, true);
|
|
672
|
+
assert.strictEqual(result.importedCount, 1);
|
|
673
|
+
// Verify the YAML was updated
|
|
674
|
+
const updatedContent = readFileSync(sprintPath, 'utf-8');
|
|
675
|
+
assert.ok(updatedContent.includes('47-4'));
|
|
676
|
+
assert.ok(updatedContent.includes('Bidirectional sync'));
|
|
677
|
+
});
|
|
678
|
+
it('should preserve existing story order and add new at end', async () => {
|
|
679
|
+
const sprintYaml = `sprint:
|
|
680
|
+
number: 11
|
|
681
|
+
epics:
|
|
682
|
+
- id: epic-47
|
|
683
|
+
stories:
|
|
684
|
+
- id: "47-1"
|
|
685
|
+
status: done
|
|
686
|
+
- id: "47-2"
|
|
687
|
+
status: done
|
|
688
|
+
`;
|
|
689
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
690
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
691
|
+
const missingStories = [
|
|
692
|
+
{ key: 'MSSCI-11800', summary: '47-4: New story', status: 'To Do', storyId: '47-4' }
|
|
693
|
+
];
|
|
694
|
+
const result = await importMissingStoriesToYaml({
|
|
695
|
+
sprintPath,
|
|
696
|
+
missingStories,
|
|
697
|
+
targetEpicId: 'epic-47'
|
|
698
|
+
});
|
|
699
|
+
assert.strictEqual(result.success, true);
|
|
700
|
+
const updatedContent = readFileSync(sprintPath, 'utf-8');
|
|
701
|
+
const idx47_1 = updatedContent.indexOf('47-1');
|
|
702
|
+
const idx47_2 = updatedContent.indexOf('47-2');
|
|
703
|
+
const idx47_4 = updatedContent.indexOf('47-4');
|
|
704
|
+
assert.ok(idx47_1 < idx47_2, '47-1 should come before 47-2');
|
|
705
|
+
assert.ok(idx47_2 < idx47_4, '47-4 should come after 47-2');
|
|
706
|
+
});
|
|
707
|
+
it('should support dry-run mode that does not modify file', async () => {
|
|
708
|
+
const sprintYaml = `sprint:
|
|
709
|
+
number: 11
|
|
710
|
+
epics:
|
|
711
|
+
- id: epic-47
|
|
712
|
+
stories:
|
|
713
|
+
- id: "47-1"
|
|
714
|
+
status: done
|
|
715
|
+
`;
|
|
716
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
717
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
718
|
+
const missingStories = [
|
|
719
|
+
{ key: 'MSSCI-11800', summary: '47-4: New story', status: 'To Do', storyId: '47-4' }
|
|
720
|
+
];
|
|
721
|
+
const result = await importMissingStoriesToYaml({
|
|
722
|
+
sprintPath,
|
|
723
|
+
missingStories,
|
|
724
|
+
targetEpicId: 'epic-47',
|
|
725
|
+
dryRun: true
|
|
726
|
+
});
|
|
727
|
+
assert.strictEqual(result.success, true);
|
|
728
|
+
assert.strictEqual(result.importedCount, 1);
|
|
729
|
+
assert.ok(result.wouldImport, 'Should indicate what would be imported');
|
|
730
|
+
// File should NOT be modified
|
|
731
|
+
const content = readFileSync(sprintPath, 'utf-8');
|
|
732
|
+
assert.ok(!content.includes('47-4'), 'File should not contain 47-4 in dry-run');
|
|
733
|
+
});
|
|
734
|
+
it('should fail if target epic does not exist', async () => {
|
|
735
|
+
const sprintYaml = `sprint:
|
|
736
|
+
number: 11
|
|
737
|
+
epics:
|
|
738
|
+
- id: epic-31
|
|
739
|
+
stories:
|
|
740
|
+
- id: "31-1"
|
|
741
|
+
status: done
|
|
742
|
+
`;
|
|
743
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
744
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
745
|
+
const missingStories = [
|
|
746
|
+
{ key: 'MSSCI-11800', summary: '47-4: New story', status: 'To Do', storyId: '47-4' }
|
|
747
|
+
];
|
|
748
|
+
const result = await importMissingStoriesToYaml({
|
|
749
|
+
sprintPath,
|
|
750
|
+
missingStories,
|
|
751
|
+
targetEpicId: 'epic-47' // Does not exist!
|
|
752
|
+
});
|
|
753
|
+
assert.strictEqual(result.success, false);
|
|
754
|
+
assert.ok(result.error?.includes('epic-47') || result.error?.includes('not found'));
|
|
755
|
+
});
|
|
756
|
+
it('should map Jira status to YAML status', async () => {
|
|
757
|
+
const sprintYaml = `sprint:
|
|
758
|
+
number: 11
|
|
759
|
+
epics:
|
|
760
|
+
- id: epic-47
|
|
761
|
+
stories:
|
|
762
|
+
- id: "47-1"
|
|
763
|
+
status: done
|
|
764
|
+
`;
|
|
765
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
766
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
767
|
+
const missingStories = [
|
|
768
|
+
{ key: 'MSSCI-100', summary: '47-2: Story', status: 'In Progress', storyId: '47-2' },
|
|
769
|
+
{ key: 'MSSCI-101', summary: '47-3: Story', status: 'Done', storyId: '47-3' },
|
|
770
|
+
{ key: 'MSSCI-102', summary: '47-4: Story', status: 'To Do', storyId: '47-4' }
|
|
771
|
+
];
|
|
772
|
+
const result = await importMissingStoriesToYaml({
|
|
773
|
+
sprintPath,
|
|
774
|
+
missingStories,
|
|
775
|
+
targetEpicId: 'epic-47'
|
|
776
|
+
});
|
|
777
|
+
assert.strictEqual(result.success, true);
|
|
778
|
+
const content = readFileSync(sprintPath, 'utf-8');
|
|
779
|
+
assert.ok(content.includes('status: in_progress') || content.includes('status: in-progress'));
|
|
780
|
+
assert.ok(content.includes('status: done'));
|
|
781
|
+
assert.ok(content.includes('status: backlog') || content.includes('status: todo'));
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
// ============================================
|
|
785
|
+
// Integration: Full detection flow
|
|
786
|
+
// ============================================
|
|
787
|
+
describe('Integration: Detect and report Jira-only stories', () => {
|
|
788
|
+
it('should detect stories in Jira sprint but missing from YAML', async () => {
|
|
789
|
+
// Setup: YAML with stories 47-1, 47-2, 47-3
|
|
790
|
+
const sprintYaml = `sprint:
|
|
791
|
+
number: 11
|
|
792
|
+
jira_sprint_id: 275
|
|
793
|
+
status: active
|
|
794
|
+
epics:
|
|
795
|
+
- id: epic-47
|
|
796
|
+
title: Jira Sync
|
|
797
|
+
stories:
|
|
798
|
+
- id: "47-1"
|
|
799
|
+
title: Auto-create Jira epic
|
|
800
|
+
jira: MSSCI-11797
|
|
801
|
+
status: done
|
|
802
|
+
- id: "47-2"
|
|
803
|
+
title: Sync sprint numbers
|
|
804
|
+
jira: MSSCI-11798
|
|
805
|
+
status: done
|
|
806
|
+
- id: "47-3"
|
|
807
|
+
title: Detect Jira-only stories
|
|
808
|
+
jira: MSSCI-11799
|
|
809
|
+
status: in_progress
|
|
810
|
+
`;
|
|
811
|
+
const sprintPath = join(TEST_DIR, 'current-sprint.yaml');
|
|
812
|
+
writeFileSync(sprintPath, sprintYaml);
|
|
813
|
+
// Step 1: Get YAML story IDs
|
|
814
|
+
const yamlResult = await getYamlStoryIds({ sprintPath });
|
|
815
|
+
assert.strictEqual(yamlResult.success, true);
|
|
816
|
+
assert.strictEqual(yamlResult.storyIds?.length, 3);
|
|
817
|
+
// Step 2: Mock Jira sprint issues (includes 47-4 and 47-5 not in YAML)
|
|
818
|
+
const jiraIssues = [
|
|
819
|
+
{ key: 'MSSCI-11797', summary: '47-1: Auto-create Jira epic', status: 'Done', labels: ['pennyfarthing'] },
|
|
820
|
+
{ key: 'MSSCI-11798', summary: '47-2: Sync sprint numbers', status: 'Done', labels: ['pennyfarthing'] },
|
|
821
|
+
{ key: 'MSSCI-11799', summary: '47-3: Detect Jira-only stories', status: 'In Progress', labels: ['pennyfarthing'] },
|
|
822
|
+
{ key: 'MSSCI-11800', summary: '47-4: Bidirectional sync', status: 'To Do', labels: ['pennyfarthing'] },
|
|
823
|
+
{ key: 'MSSCI-11801', summary: '47-5: Retrofit epics', status: 'To Do', labels: ['pennyfarthing'] }
|
|
824
|
+
];
|
|
825
|
+
// Step 3: Find Jira-only stories
|
|
826
|
+
const compareResult = await findJiraOnlyStories({
|
|
827
|
+
jiraIssues,
|
|
828
|
+
yamlStoryIds: yamlResult.storyIds,
|
|
829
|
+
filterLabel: 'pennyfarthing'
|
|
830
|
+
});
|
|
831
|
+
assert.strictEqual(compareResult.success, true);
|
|
832
|
+
assert.strictEqual(compareResult.missingStories?.length, 2);
|
|
833
|
+
// Step 4: Generate report
|
|
834
|
+
const reportResult = await formatMissingStoriesReport({
|
|
835
|
+
missingStories: compareResult.missingStories,
|
|
836
|
+
jiraBaseUrl: 'https://1898andco.atlassian.net'
|
|
837
|
+
});
|
|
838
|
+
assert.strictEqual(reportResult.success, true);
|
|
839
|
+
assert.ok(reportResult.report?.includes('47-4'));
|
|
840
|
+
assert.ok(reportResult.report?.includes('47-5'));
|
|
841
|
+
assert.ok(reportResult.report?.includes('2 stories'));
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
//# sourceMappingURL=jira-sprint-sync.test.js.map
|