@pennyfarthing/core 10.0.3 → 10.1.0
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 +9 -7
- package/package.json +7 -1
- package/packages/core/dist/cli/commands/cyclist.d.ts +5 -1
- package/packages/core/dist/cli/commands/cyclist.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/cyclist.js +4 -4
- package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
- package/packages/core/dist/cli/commands/cyclist.test.js +2 -2
- package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +17 -16
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +251 -4
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts +7 -0
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +43 -7
- 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 +26 -0
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/index.js +1 -1
- package/packages/core/dist/cli/index.js.map +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
- package/packages/core/dist/cli/utils/files.d.ts +10 -0
- package/packages/core/dist/cli/utils/files.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/files.js +35 -0
- package/packages/core/dist/cli/utils/files.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +22 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/python.js +102 -0
- package/packages/core/dist/cli/utils/python.js.map +1 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +10 -0
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-report.js +11 -7
- package/packages/core/dist/scripts/generate-report.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider-report.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-spider-report.js +12 -8
- package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-spider.js +6 -4
- package/packages/core/dist/scripts/generate-spider.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider.test.js +2 -2
- package/packages/core/dist/scripts/generate-spider.test.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +1 -3
- package/pennyfarthing-dist/agents/architect.md +0 -6
- package/pennyfarthing-dist/agents/devops.md +0 -6
- package/pennyfarthing-dist/agents/orchestrator.md +0 -6
- package/pennyfarthing-dist/agents/pm.md +1 -7
- package/pennyfarthing-dist/agents/sm-finish.md +1 -1
- package/pennyfarthing-dist/agents/sm-setup.md +2 -2
- package/pennyfarthing-dist/agents/sm.md +4 -11
- package/pennyfarthing-dist/commands/architect.md +11 -3
- package/pennyfarthing-dist/commands/close-epic.md +24 -131
- package/pennyfarthing-dist/commands/create-theme.md +14 -24
- package/pennyfarthing-dist/commands/dev.md +11 -3
- package/pennyfarthing-dist/commands/devops.md +11 -3
- package/pennyfarthing-dist/commands/health-check.md +1 -3
- package/pennyfarthing-dist/commands/help.md +8 -12
- package/pennyfarthing-dist/commands/list-themes.md +14 -16
- package/pennyfarthing-dist/commands/orchestrator.md +11 -3
- package/pennyfarthing-dist/commands/parallel-work.md +1 -3
- package/pennyfarthing-dist/commands/pm.md +11 -3
- package/pennyfarthing-dist/commands/prime.md +6 -6
- package/pennyfarthing-dist/commands/repo-status.md +2 -2
- package/pennyfarthing-dist/commands/reviewer.md +11 -3
- package/pennyfarthing-dist/commands/run-ci.md +1 -1
- package/pennyfarthing-dist/commands/set-theme.md +14 -51
- package/pennyfarthing-dist/commands/setup.md +1 -1
- package/pennyfarthing-dist/commands/show-theme.md +14 -16
- package/pennyfarthing-dist/commands/sm.md +11 -3
- package/pennyfarthing-dist/commands/sprint.md +8 -8
- package/pennyfarthing-dist/commands/tea.md +11 -3
- package/pennyfarthing-dist/commands/tech-writer.md +11 -3
- package/pennyfarthing-dist/commands/theme-maker.md +14 -671
- package/pennyfarthing-dist/commands/theme.md +95 -0
- package/pennyfarthing-dist/commands/ux-designer.md +11 -3
- package/pennyfarthing-dist/commands/work.md +3 -5
- package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
- package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
- package/pennyfarthing-dist/guides/hooks.md +5 -5
- package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
- package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
- package/pennyfarthing-dist/guides/prime.md +2 -2
- package/pennyfarthing-dist/guides/skill-schema.md +25 -26
- package/pennyfarthing-dist/guides/xml-tags.md +2 -2
- package/pennyfarthing-dist/scripts/README.md +2 -2
- package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +1 -1
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +3 -3
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +14 -12
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +3 -3
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
- package/pennyfarthing-dist/scripts/sprint/README.md +32 -17
- package/pennyfarthing-dist/scripts/story/README.md +1 -1
- package/pennyfarthing-dist/scripts/test/test-setup.sh +1 -1
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
- package/pennyfarthing-dist/scripts/theme/README.md +1 -1
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +62 -17
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/skill-registry.yaml +41 -28
- package/pennyfarthing-dist/skills/sprint/skill.md +386 -68
- package/pennyfarthing-dist/skills/story/skill.md +14 -206
- package/pennyfarthing-dist/skills/theme/skill.md +290 -75
- package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
- package/pennyfarthing-dist/skills/workflow/skill.md +4 -4
- package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
- package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +42 -25
- package/pennyfarthing-dist/workflows/git-cleanup.yaml +1 -1
- package/pennyfarthing-dist/workflows/project-setup/steps/step-10-complete.md +1 -1
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/cli.py +15 -0
- package/pennyfarthing_scripts/codemarkers/__init__.py +19 -0
- package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/analyze.py +326 -0
- package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
- package/pennyfarthing_scripts/codemarkers/models.py +45 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +2 -1
- package/pennyfarthing_scripts/complexity/__init__.py +15 -0
- package/pennyfarthing_scripts/complexity/__main__.py +6 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/analyze.py +207 -0
- package/pennyfarthing_scripts/complexity/cli.py +78 -0
- package/pennyfarthing_scripts/complexity/formatters.py +64 -0
- package/pennyfarthing_scripts/complexity/models.py +32 -0
- package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/analyze.py +323 -0
- package/pennyfarthing_scripts/deadcode/cli.py +163 -0
- package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
- package/pennyfarthing_scripts/deadcode/models.py +54 -0
- package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
- package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
- package/pennyfarthing_scripts/dependencies/cli.py +72 -0
- package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
- package/pennyfarthing_scripts/dependencies/models.py +39 -0
- package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
- package/pennyfarthing_scripts/healthscore/__main__.py +6 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/analyze.py +161 -0
- package/pennyfarthing_scripts/healthscore/cli.py +76 -0
- package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
- package/pennyfarthing_scripts/healthscore/models.py +44 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/analyze.py +28 -1
- package/pennyfarthing_scripts/hotspots/cli.py +11 -9
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
- package/pennyfarthing_scripts/jira/cli.py +78 -1
- package/pennyfarthing_scripts/jira/client.py +28 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/workflow.py +5 -3
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +63 -6
- package/pennyfarthing_scripts/sprint/archive_epic.py +198 -85
- package/pennyfarthing_scripts/sprint/cli.py +1565 -65
- package/pennyfarthing_scripts/sprint/epic_add.py +173 -0
- package/pennyfarthing_scripts/sprint/loader.py +46 -2
- package/pennyfarthing_scripts/sprint/story_add.py +202 -27
- package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
- package/pennyfarthing_scripts/sprint/validate_cmd.py +44 -5
- package/pennyfarthing_scripts/sprint/validator.py +13 -3
- package/pennyfarthing_scripts/sprint/work.py +43 -3
- package/pennyfarthing_scripts/sprint/yaml_io.py +124 -15
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
- package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
- package/pennyfarthing_scripts/tests/test_sprint_package.py +166 -0
- package/pennyfarthing_scripts/tests/test_yaml_io.py +117 -0
- package/pennyfarthing_scripts/theme/__init__.py +5 -0
- package/pennyfarthing_scripts/theme/__main__.py +6 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/cli.py +286 -0
- package/scripts/README.md +53 -0
- package/scripts/postinstall.cjs +34 -0
- package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
- package/pennyfarthing-dist/scripts/sprint/archive-story.sh +0 -133
- package/pennyfarthing-dist/scripts/sprint/available-stories.sh +0 -91
- package/pennyfarthing-dist/scripts/sprint/check-story.sh +0 -158
- package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +0 -52
- package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +0 -63
- package/pennyfarthing-dist/scripts/sprint/list-future.sh +0 -145
- package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +0 -110
- package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +0 -148
- package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +0 -415
- package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +0 -33
- package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +0 -230
- package/pennyfarthing-dist/scripts/sprint/sprint-status.sh +0 -134
- package/pennyfarthing-dist/scripts/sprint/validate-sprint-yaml.sh +0 -139
- package/pennyfarthing-dist/skills/sprint/scripts/archive-story.sh +0 -101
- package/pennyfarthing-dist/skills/sprint/scripts/available-stories.sh +0 -97
- package/pennyfarthing-dist/skills/sprint/scripts/check-story.sh +0 -164
- package/pennyfarthing-dist/skills/sprint/scripts/create-jira-epic.sh +0 -23
- package/pennyfarthing-dist/skills/sprint/scripts/new-sprint.sh +0 -116
- package/pennyfarthing-dist/skills/sprint/scripts/promote-epic.sh +0 -164
- package/pennyfarthing-dist/skills/sprint/scripts/sprint-info.sh +0 -39
- package/pennyfarthing-dist/skills/sprint/scripts/sprint-status.sh +0 -147
- package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +0 -23
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Initialize a new sprint from template
|
|
3
|
-
# Usage: .pennyfarthing/scripts/sprint/new-sprint.sh <sprint-yyww> <jira-id> <start-date> <end-date> "<goal>"
|
|
4
|
-
#
|
|
5
|
-
# Example: .pennyfarthing/scripts/sprint/new-sprint.sh 2605 277 2026-02-03 2026-02-16 "Polish and stabilization"
|
|
6
|
-
|
|
7
|
-
set -euo pipefail
|
|
8
|
-
|
|
9
|
-
SPRINT_YYWW="${1:-}"
|
|
10
|
-
JIRA_ID="${2:-}"
|
|
11
|
-
START_DATE="${3:-}"
|
|
12
|
-
END_DATE="${4:-}"
|
|
13
|
-
GOAL="${5:-}"
|
|
14
|
-
|
|
15
|
-
if [[ -z "$SPRINT_YYWW" || -z "$JIRA_ID" || -z "$START_DATE" || -z "$END_DATE" || -z "$GOAL" ]]; then
|
|
16
|
-
echo "Usage: new-sprint.sh <sprint-yyww> <jira-id> <start-date> <end-date> \"<goal>\""
|
|
17
|
-
echo ""
|
|
18
|
-
echo "Arguments:"
|
|
19
|
-
echo " sprint-yyww Sprint identifier in YYWW format (e.g., 2605 for 2026 week 5)"
|
|
20
|
-
echo " jira-id Jira sprint ID number (e.g., 277)"
|
|
21
|
-
echo " start-date Sprint start date YYYY-MM-DD"
|
|
22
|
-
echo " end-date Sprint end date YYYY-MM-DD"
|
|
23
|
-
echo " goal Sprint goal (quoted string)"
|
|
24
|
-
echo ""
|
|
25
|
-
echo "Example:"
|
|
26
|
-
echo " new-sprint.sh 2605 277 2026-02-03 2026-02-16 \"Polish and stabilization\""
|
|
27
|
-
exit 1
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
# Find project root
|
|
31
|
-
source "$(dirname "${BASH_SOURCE[0]}")/../lib/find-root.sh"
|
|
32
|
-
TEMPLATE_FILE="$PROJECT_ROOT/sprint/sprint-template.yaml"
|
|
33
|
-
SPRINT_FILE="$PROJECT_ROOT/sprint/current-sprint.yaml"
|
|
34
|
-
ARCHIVE_FILE="$PROJECT_ROOT/sprint/archive/sprint-${SPRINT_YYWW}-completed.yaml"
|
|
35
|
-
|
|
36
|
-
# Check template exists
|
|
37
|
-
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
|
38
|
-
echo "Error: Template file not found at $TEMPLATE_FILE"
|
|
39
|
-
exit 1
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# Warn if current sprint file exists and is active
|
|
43
|
-
if [[ -f "$SPRINT_FILE" ]]; then
|
|
44
|
-
if ! command -v yq &> /dev/null; then
|
|
45
|
-
echo "Warning: yq not installed, cannot check current sprint status"
|
|
46
|
-
else
|
|
47
|
-
CURRENT_STATUS=$(yq eval '.sprint.status' "$SPRINT_FILE")
|
|
48
|
-
if [[ "$CURRENT_STATUS" == "active" ]]; then
|
|
49
|
-
echo "Warning: Current sprint is still active!"
|
|
50
|
-
echo "Current sprint file will be overwritten."
|
|
51
|
-
echo ""
|
|
52
|
-
read -p "Continue? [y/N] " -n 1 -r
|
|
53
|
-
echo
|
|
54
|
-
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
55
|
-
echo "Aborted."
|
|
56
|
-
exit 1
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
# Create new sprint file
|
|
63
|
-
cat > "$SPRINT_FILE" << EOF
|
|
64
|
-
sprint:
|
|
65
|
-
name: "TO Sprint $SPRINT_YYWW"
|
|
66
|
-
jira_sprint_id: $JIRA_ID
|
|
67
|
-
jira_sprint_name: "TO Sprint $SPRINT_YYWW"
|
|
68
|
-
goal: $GOAL
|
|
69
|
-
start_date: $START_DATE
|
|
70
|
-
end_date: $END_DATE
|
|
71
|
-
status: active
|
|
72
|
-
|
|
73
|
-
# Completed stories archived to: sprint/archive/sprint-${SPRINT_YYWW}-completed.yaml
|
|
74
|
-
|
|
75
|
-
epics:
|
|
76
|
-
# Add epics and stories here
|
|
77
|
-
# See sprint/sprint-template.yaml for format reference
|
|
78
|
-
EOF
|
|
79
|
-
|
|
80
|
-
echo "Created $SPRINT_FILE"
|
|
81
|
-
|
|
82
|
-
# Create archive file
|
|
83
|
-
cat > "$ARCHIVE_FILE" << EOF
|
|
84
|
-
# Sprint TO Sprint $SPRINT_YYWW - Completed Stories
|
|
85
|
-
# Jira Sprint ID: $JIRA_ID
|
|
86
|
-
# Archived: $(date +%Y-%m-%d)
|
|
87
|
-
|
|
88
|
-
sprint:
|
|
89
|
-
name: "TO Sprint $SPRINT_YYWW"
|
|
90
|
-
jira_sprint_id: $JIRA_ID
|
|
91
|
-
jira_sprint_name: "TO Sprint $SPRINT_YYWW"
|
|
92
|
-
goal: $GOAL
|
|
93
|
-
|
|
94
|
-
completed:
|
|
95
|
-
# Completed stories will be appended here by archive-story.sh
|
|
96
|
-
EOF
|
|
97
|
-
|
|
98
|
-
echo "Created $ARCHIVE_FILE"
|
|
99
|
-
|
|
100
|
-
echo ""
|
|
101
|
-
echo "New sprint initialized:"
|
|
102
|
-
echo " Name: TO Sprint $SPRINT_YYWW"
|
|
103
|
-
echo " Jira ID: $JIRA_ID"
|
|
104
|
-
echo " Dates: $START_DATE to $END_DATE"
|
|
105
|
-
echo " Goal: $GOAL"
|
|
106
|
-
echo ""
|
|
107
|
-
echo "Next steps:"
|
|
108
|
-
echo " 1. Add epics and stories to $SPRINT_FILE"
|
|
109
|
-
echo " 2. Use sprint-status.sh to check progress"
|
|
110
|
-
echo " 3. Use archive-story.sh to move completed stories"
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Promote an epic from future.yaml to current-sprint.yaml
|
|
3
|
-
# Usage: .pennyfarthing/scripts/sprint/promote-epic.sh <epic-id>
|
|
4
|
-
#
|
|
5
|
-
# Example: .pennyfarthing/scripts/sprint/promote-epic.sh epic-41
|
|
6
|
-
#
|
|
7
|
-
# Features:
|
|
8
|
-
# - Detects ID collisions and assigns new ID if needed
|
|
9
|
-
# - Uses yq for proper YAML array insertion
|
|
10
|
-
# - Automatically removes from future.yaml after successful promotion
|
|
11
|
-
|
|
12
|
-
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
EPIC_ID="${1:-}"
|
|
15
|
-
|
|
16
|
-
if [[ -z "$EPIC_ID" ]]; then
|
|
17
|
-
echo "Usage: promote-epic.sh <epic-id>"
|
|
18
|
-
echo "Example: promote-epic.sh epic-41"
|
|
19
|
-
echo ""
|
|
20
|
-
echo "Moves an epic and its stories from future.yaml to current-sprint.yaml"
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# Find project root
|
|
25
|
-
source "$(dirname "${BASH_SOURCE[0]}")/../lib/find-root.sh"
|
|
26
|
-
|
|
27
|
-
FUTURE_FILE="$PROJECT_ROOT/sprint/future.yaml"
|
|
28
|
-
SPRINT_FILE="$PROJECT_ROOT/sprint/current-sprint.yaml"
|
|
29
|
-
|
|
30
|
-
if [[ ! -f "$FUTURE_FILE" ]]; then
|
|
31
|
-
echo "Error: Future file not found at $FUTURE_FILE"
|
|
32
|
-
exit 1
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
if [[ ! -f "$SPRINT_FILE" ]]; then
|
|
36
|
-
echo "Error: Sprint file not found at $SPRINT_FILE"
|
|
37
|
-
exit 1
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
if ! command -v yq &> /dev/null; then
|
|
41
|
-
echo "Error: yq is required but not installed"
|
|
42
|
-
echo "Install with: brew install yq"
|
|
43
|
-
exit 1
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# Find the epic in future.yaml
|
|
47
|
-
# Epics are nested under future.initiatives[].epics[]
|
|
48
|
-
EPIC_DATA=$(yq -o json ".future.initiatives[].epics[] | select(.id == \"$EPIC_ID\")" "$FUTURE_FILE" 2>/dev/null || echo "")
|
|
49
|
-
|
|
50
|
-
if [[ -z "$EPIC_DATA" || "$EPIC_DATA" == "null" ]]; then
|
|
51
|
-
echo "Error: Epic $EPIC_ID not found in $FUTURE_FILE"
|
|
52
|
-
echo ""
|
|
53
|
-
echo "Available epics:"
|
|
54
|
-
yq '.future.initiatives[].epics[].id' "$FUTURE_FILE" 2>/dev/null || echo " None found"
|
|
55
|
-
exit 1
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
# Check for ID collision in current-sprint.yaml
|
|
59
|
-
EXISTING_ID=$(yq ".epics[] | select(.id == \"$EPIC_ID\") | .id" "$SPRINT_FILE" 2>/dev/null || echo "")
|
|
60
|
-
|
|
61
|
-
NEW_EPIC_ID="$EPIC_ID"
|
|
62
|
-
if [[ -n "$EXISTING_ID" && "$EXISTING_ID" != "null" ]]; then
|
|
63
|
-
echo "Warning: Epic ID $EPIC_ID already exists in current sprint."
|
|
64
|
-
|
|
65
|
-
# Find the highest epic-N ID and increment
|
|
66
|
-
MAX_EPIC_NUM=$(yq '.epics[].id' "$SPRINT_FILE" 2>/dev/null | grep -oE 'epic-[0-9]+' | sed 's/epic-//' | sort -n | tail -1 || echo "0")
|
|
67
|
-
if [[ -z "$MAX_EPIC_NUM" ]]; then
|
|
68
|
-
MAX_EPIC_NUM=0
|
|
69
|
-
fi
|
|
70
|
-
NEW_EPIC_NUM=$((MAX_EPIC_NUM + 1))
|
|
71
|
-
NEW_EPIC_ID="epic-$NEW_EPIC_NUM"
|
|
72
|
-
|
|
73
|
-
echo "Assigning new ID: $NEW_EPIC_ID"
|
|
74
|
-
echo ""
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
# Extract epic fields
|
|
78
|
-
EPIC_TITLE=$(echo "$EPIC_DATA" | yq -r '.title // "Unknown"')
|
|
79
|
-
EPIC_DESCRIPTION=$(echo "$EPIC_DATA" | yq -r '.description // ""')
|
|
80
|
-
EPIC_POINTS=$(echo "$EPIC_DATA" | yq -r '.points // 0')
|
|
81
|
-
EPIC_PRIORITY=$(echo "$EPIC_DATA" | yq -r '.priority // "P2"')
|
|
82
|
-
EPIC_REPOS=$(echo "$EPIC_DATA" | yq -r '.repos // "pennyfarthing"')
|
|
83
|
-
STORY_COUNT=$(echo "$EPIC_DATA" | yq '[.stories[]] | length')
|
|
84
|
-
|
|
85
|
-
echo ""
|
|
86
|
-
echo "Promoting epic to current sprint:"
|
|
87
|
-
echo " Original ID: $EPIC_ID"
|
|
88
|
-
if [[ "$NEW_EPIC_ID" != "$EPIC_ID" ]]; then
|
|
89
|
-
echo " New ID: $NEW_EPIC_ID"
|
|
90
|
-
fi
|
|
91
|
-
echo " Title: $EPIC_TITLE"
|
|
92
|
-
echo " Points: $EPIC_POINTS"
|
|
93
|
-
echo " Priority: $EPIC_PRIORITY"
|
|
94
|
-
echo " Stories: $STORY_COUNT"
|
|
95
|
-
echo ""
|
|
96
|
-
|
|
97
|
-
# Build the new epic object as JSON, then use yq to append it properly
|
|
98
|
-
# This ensures valid YAML structure
|
|
99
|
-
|
|
100
|
-
# Extract old epic ID prefix for updating story IDs (e.g., "64" from "epic-64")
|
|
101
|
-
OLD_ID_NUM=$(echo "$EPIC_ID" | sed 's/epic-//')
|
|
102
|
-
NEW_ID_NUM=$(echo "$NEW_EPIC_ID" | sed 's/epic-//')
|
|
103
|
-
|
|
104
|
-
# Create a temp file for the new epic
|
|
105
|
-
TEMP_EPIC=$(mktemp)
|
|
106
|
-
trap "rm -f $TEMP_EPIC" EXIT
|
|
107
|
-
|
|
108
|
-
# Transform the epic data: update IDs, add required fields, format for current-sprint.yaml
|
|
109
|
-
# Note: Using -o yaml for Go yq (not -y which is Python yq)
|
|
110
|
-
echo "$EPIC_DATA" | yq -o yaml "
|
|
111
|
-
.id = \"$NEW_EPIC_ID\" |
|
|
112
|
-
.type = \"epic\" |
|
|
113
|
-
.title = \"Epic: \" + .title |
|
|
114
|
-
.status = \"backlog\" |
|
|
115
|
-
.stories = [.stories[] |
|
|
116
|
-
.id = ((.id | tostring) | sub(\"^${OLD_ID_NUM}-\"; \"${NEW_ID_NUM}-\")) |
|
|
117
|
-
.status = \"backlog\" |
|
|
118
|
-
.repos = (.repos // \"pennyfarthing\") |
|
|
119
|
-
.workflow = (.workflow // \"tdd\") |
|
|
120
|
-
.priority = (.priority // \"P2\") |
|
|
121
|
-
.acceptance_criteria = (.acceptance_criteria // [])
|
|
122
|
-
]
|
|
123
|
-
" > "$TEMP_EPIC"
|
|
124
|
-
|
|
125
|
-
echo "Epic to add:"
|
|
126
|
-
echo "---"
|
|
127
|
-
cat "$TEMP_EPIC"
|
|
128
|
-
echo "---"
|
|
129
|
-
echo ""
|
|
130
|
-
|
|
131
|
-
# Use yq to properly append the epic to the epics array
|
|
132
|
-
yq eval -i ".epics += [$(cat "$TEMP_EPIC" | yq -o json)]" "$SPRINT_FILE"
|
|
133
|
-
|
|
134
|
-
echo "Successfully added epic to $SPRINT_FILE"
|
|
135
|
-
|
|
136
|
-
# Remove from future.yaml
|
|
137
|
-
echo ""
|
|
138
|
-
echo "Removing from future.yaml..."
|
|
139
|
-
yq eval -i "del(.future.initiatives[].epics[] | select(.id == \"$EPIC_ID\"))" "$FUTURE_FILE"
|
|
140
|
-
echo "Removed $EPIC_ID from future.yaml"
|
|
141
|
-
|
|
142
|
-
echo ""
|
|
143
|
-
echo "Promotion complete!"
|
|
144
|
-
echo ""
|
|
145
|
-
echo "Next steps:"
|
|
146
|
-
echo " 1. Review the epic in $SPRINT_FILE"
|
|
147
|
-
echo " 2. Create Jira epic: .pennyfarthing/scripts/jira/create-jira-epic.sh $NEW_EPIC_ID"
|
|
148
|
-
echo " 3. Start work: /sprint work ${NEW_ID_NUM}-1"
|
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env zsh
|
|
2
|
-
# Sprint Common Functions
|
|
3
|
-
# Shared functions for sprint management
|
|
4
|
-
#
|
|
5
|
-
# Usage: Scripts must set up PROJECT_ROOT before sourcing this file:
|
|
6
|
-
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
|
|
7
|
-
# source "$SCRIPT_DIR/../lib/find-root.sh"
|
|
8
|
-
# source "$SCRIPT_DIR/sprint-common.sh"
|
|
9
|
-
|
|
10
|
-
# Jira project identifier
|
|
11
|
-
export JIRA_PROJECT="MSSCI"
|
|
12
|
-
|
|
13
|
-
# Require PROJECT_ROOT to be set
|
|
14
|
-
if [[ -z "${PROJECT_ROOT:-}" ]]; then
|
|
15
|
-
echo "Error: PROJECT_ROOT must be set before sourcing sprint-common.sh" >&2
|
|
16
|
-
echo "Source lib/find-root.sh first" >&2
|
|
17
|
-
exit 1
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# find_story_file STORY_KEY
|
|
21
|
-
# Search for a story in sprint YAML files
|
|
22
|
-
# Returns: path to the YAML file containing the story
|
|
23
|
-
find_story_file() {
|
|
24
|
-
local story_key="$1"
|
|
25
|
-
|
|
26
|
-
# Extract epic number from story key (e.g., "11-9" from "11-9-theme-maker")
|
|
27
|
-
local epic_num="${story_key%%-*}"
|
|
28
|
-
|
|
29
|
-
# Check current sprint first
|
|
30
|
-
if [[ -f "$PROJECT_ROOT/sprint/current-sprint.yaml" ]]; then
|
|
31
|
-
# Simple check: grep for the story key in the file
|
|
32
|
-
if grep -q "id: \"$epic_num-" "$PROJECT_ROOT/sprint/current-sprint.yaml" 2>/dev/null; then
|
|
33
|
-
echo "$PROJECT_ROOT/sprint/current-sprint.yaml"
|
|
34
|
-
return 0
|
|
35
|
-
fi
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
# Check archived sprints
|
|
39
|
-
local archive_dir="$PROJECT_ROOT/sprint/archive"
|
|
40
|
-
if [[ -d "$archive_dir" ]]; then
|
|
41
|
-
local found=$(grep -l "id: \"$epic_num-" "$archive_dir"/*.yaml 2>/dev/null | head -1)
|
|
42
|
-
if [[ -n "$found" ]]; then
|
|
43
|
-
echo "$found"
|
|
44
|
-
return 0
|
|
45
|
-
fi
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
return 1
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
# get_story_field STORY_KEY FIELD_NAME
|
|
52
|
-
# Extract a field value from a story in sprint YAML
|
|
53
|
-
# Returns: the field value or "null"
|
|
54
|
-
get_story_field() {
|
|
55
|
-
local story_key="$1"
|
|
56
|
-
local field_name="$2"
|
|
57
|
-
|
|
58
|
-
# Extract epic and story numbers from story key
|
|
59
|
-
local epic_num="${story_key%%-*}"
|
|
60
|
-
local story_rest="${story_key#*-}"
|
|
61
|
-
local story_num="${story_rest%%-*}"
|
|
62
|
-
|
|
63
|
-
local story_file=$(find_story_file "$story_key")
|
|
64
|
-
if [[ -z "$story_file" ]]; then
|
|
65
|
-
echo "null"
|
|
66
|
-
return 1
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
# Use yq to extract the field if available, otherwise fallback to grep
|
|
70
|
-
if command -v yq &> /dev/null; then
|
|
71
|
-
yq eval ".epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\") | .$field_name" "$story_file" 2>/dev/null || echo "null"
|
|
72
|
-
else
|
|
73
|
-
# Fallback: grep for the pattern and extract value
|
|
74
|
-
# This is a simplified approach - full YAML parsing is better
|
|
75
|
-
grep -A 50 "id: \"$story_key" "$story_file" 2>/dev/null | grep "$field_name:" | head -1 | sed "s/.*$field_name:[[:space:]]*\(.*\)/\1/" || echo "null"
|
|
76
|
-
fi
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# extract_jira_key JIRA_URL_OR_KEY
|
|
80
|
-
# Extract Jira issue key from URL or return as-is
|
|
81
|
-
# Returns: MSSCI-12345 format
|
|
82
|
-
extract_jira_key() {
|
|
83
|
-
local input="$1"
|
|
84
|
-
|
|
85
|
-
# If already in key format, return as-is
|
|
86
|
-
if [[ "$input" =~ ^${JIRA_PROJECT}-[0-9]+$ ]]; then
|
|
87
|
-
echo "$input"
|
|
88
|
-
return 0
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
# If it's a URL, extract the key
|
|
92
|
-
if [[ "$input" =~ ${JIRA_PROJECT}-[0-9]+ ]]; then
|
|
93
|
-
grep -o "${JIRA_PROJECT}-[0-9]\+" <<< "$input"
|
|
94
|
-
return 0
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Return input as-is if no extraction possible
|
|
98
|
-
echo "$input"
|
|
99
|
-
return 1
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
# extract_story_id BRANCH_NAME
|
|
103
|
-
# Extract story ID from branch name pattern feat/X-Y-*
|
|
104
|
-
# Returns: X-Y (e.g., "8-1" from "feat/8-1-merge-detection")
|
|
105
|
-
# Returns empty string for non-matching branches
|
|
106
|
-
extract_story_id() {
|
|
107
|
-
local branch="$1"
|
|
108
|
-
|
|
109
|
-
# Match pattern: feat/EPIC-STORY-description or feat/EPIC-STORY
|
|
110
|
-
# Uses basic regex compatible with both bash and zsh
|
|
111
|
-
if [[ "$branch" =~ ^feat/([0-9]+-[0-9]+) ]]; then
|
|
112
|
-
echo "${match[1]:-${BASH_REMATCH[1]}}"
|
|
113
|
-
fi
|
|
114
|
-
# Returns empty string for non-matching patterns
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
# update_story_status STORY_ID [NEW_STATUS]
|
|
118
|
-
# Update story status in sprint YAML and add completed date
|
|
119
|
-
# Uses yq for YAML manipulation
|
|
120
|
-
# Arguments:
|
|
121
|
-
# STORY_ID - Story identifier (e.g., "8-1")
|
|
122
|
-
# NEW_STATUS - Optional status, defaults to "done"
|
|
123
|
-
update_story_status() {
|
|
124
|
-
local story_id="$1"
|
|
125
|
-
local new_status="${2:-done}"
|
|
126
|
-
local completed_date
|
|
127
|
-
completed_date=$(date +%Y-%m-%d)
|
|
128
|
-
|
|
129
|
-
local sprint_file="$PROJECT_ROOT/sprint/current-sprint.yaml"
|
|
130
|
-
|
|
131
|
-
if [[ ! -f "$sprint_file" ]]; then
|
|
132
|
-
return 1
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
# Check if yq is available
|
|
136
|
-
if ! command -v yq &>/dev/null; then
|
|
137
|
-
echo "Warning: yq not found, cannot update sprint YAML" >&2
|
|
138
|
-
return 1
|
|
139
|
-
fi
|
|
140
|
-
|
|
141
|
-
# Extract epic and story numbers
|
|
142
|
-
local epic_num="${story_id%%-*}"
|
|
143
|
-
local story_num="${story_id#*-}"
|
|
144
|
-
|
|
145
|
-
# Update status and add completed date using yq
|
|
146
|
-
yq eval -i "
|
|
147
|
-
(.epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\")).status = \"$new_status\" |
|
|
148
|
-
(.epics[] | select(.id == \"$epic_num\") | .stories[] | select(.id == \"$story_num\")).completed = \"$completed_date\"
|
|
149
|
-
" "$sprint_file"
|
|
150
|
-
|
|
151
|
-
return $?
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
# log_reconciliation STORY_ID MESSAGE
|
|
155
|
-
# Log reconciliation event to .session/ directory
|
|
156
|
-
# Arguments:
|
|
157
|
-
# STORY_ID - Story identifier (e.g., "8-1")
|
|
158
|
-
# MESSAGE - Optional message, defaults to "Merge detected"
|
|
159
|
-
log_reconciliation() {
|
|
160
|
-
local story_id="$1"
|
|
161
|
-
local message="${2:-Merge detected}"
|
|
162
|
-
local timestamp
|
|
163
|
-
timestamp=$(date +"%Y-%m-%d %H:%M:%S")
|
|
164
|
-
|
|
165
|
-
local session_dir="$PROJECT_ROOT/.session"
|
|
166
|
-
|
|
167
|
-
# Ensure session directory exists
|
|
168
|
-
mkdir -p "$session_dir"
|
|
169
|
-
|
|
170
|
-
local log_file="$session_dir/reconciliation.log"
|
|
171
|
-
|
|
172
|
-
echo "[$timestamp] Story $story_id: $message" >> "$log_file"
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
# detect_drift
|
|
176
|
-
# Detect stories that have been merged but not marked as done in YAML or Jira
|
|
177
|
-
# Scans git log for recent merges (past 7 days) and compares against YAML/Jira status
|
|
178
|
-
# Returns: list of drifted stories in "story_id:yaml_status:jira_status" format
|
|
179
|
-
# A story is "drifted" when its branch was merged but YAML or Jira still shows in_progress
|
|
180
|
-
detect_drift() {
|
|
181
|
-
local drifted=()
|
|
182
|
-
|
|
183
|
-
# Get recently merged branches (past 7 days)
|
|
184
|
-
# Look for feat/X-Y-* pattern in merge commit messages
|
|
185
|
-
local merged_branches
|
|
186
|
-
merged_branches=$(git log --merges --oneline --since="7 days ago" develop 2>/dev/null | \
|
|
187
|
-
grep -oE 'feat/[0-9]+-[0-9]+[^[:space:]]*' | sort -u)
|
|
188
|
-
|
|
189
|
-
for branch in $merged_branches; do
|
|
190
|
-
# Use extract_story_id to parse branch name
|
|
191
|
-
local story_id
|
|
192
|
-
story_id=$(extract_story_id "$branch")
|
|
193
|
-
|
|
194
|
-
if [[ -n "$story_id" ]]; then
|
|
195
|
-
# Check current status in YAML via get_story_field
|
|
196
|
-
local yaml_status
|
|
197
|
-
yaml_status=$(get_story_field "$story_id" "status")
|
|
198
|
-
|
|
199
|
-
# Get Jira key and check Jira status
|
|
200
|
-
local jira_key jira_status
|
|
201
|
-
jira_key=$(get_story_field "$story_id" "jira")
|
|
202
|
-
jira_status="unknown"
|
|
203
|
-
|
|
204
|
-
if [[ -n "$jira_key" && "$jira_key" != "null" ]]; then
|
|
205
|
-
# Query Jira for current status
|
|
206
|
-
jira_status=$(jira issue view "$jira_key" --raw 2>/dev/null | \
|
|
207
|
-
jq -r '.fields.status.name // "unknown"' 2>/dev/null || echo "unknown")
|
|
208
|
-
fi
|
|
209
|
-
|
|
210
|
-
# Story is drifted if merged but YAML status is not "done" and not "backlog"
|
|
211
|
-
# OR if Jira status is not "Done" (case-insensitive check)
|
|
212
|
-
local yaml_drifted=false
|
|
213
|
-
local jira_drifted=false
|
|
214
|
-
|
|
215
|
-
if [[ "$yaml_status" != "done" && "$yaml_status" != "backlog" && "$yaml_status" != "null" ]]; then
|
|
216
|
-
yaml_drifted=true
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
# Check Jira drift - status should be "Done" for merged stories
|
|
220
|
-
if [[ "$jira_status" != "unknown" && "$jira_status" != "Done" && "$jira_status" != "Closed" ]]; then
|
|
221
|
-
jira_drifted=true
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
# Report if either YAML or Jira is drifted
|
|
225
|
-
if [[ "$yaml_drifted" == "true" || "$jira_drifted" == "true" ]]; then
|
|
226
|
-
drifted+=("$story_id:$yaml_status:$jira_status")
|
|
227
|
-
fi
|
|
228
|
-
fi
|
|
229
|
-
done
|
|
230
|
-
|
|
231
|
-
# Output drifted stories (one per line)
|
|
232
|
-
printf '%s\n' "${drifted[@]}" 2>/dev/null || true
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
# =============================================================================
|
|
236
|
-
# Sprint Summary Functions
|
|
237
|
-
# =============================================================================
|
|
238
|
-
|
|
239
|
-
# get_sprint_file
|
|
240
|
-
# Returns path to current sprint YAML file
|
|
241
|
-
get_sprint_file() {
|
|
242
|
-
echo "$PROJECT_ROOT/sprint/current-sprint.yaml"
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
# check_yq
|
|
246
|
-
# Verify yq is available, return error message if not
|
|
247
|
-
# Returns: 0 if yq available, 1 if not
|
|
248
|
-
check_yq() {
|
|
249
|
-
if ! command -v yq &>/dev/null; then
|
|
250
|
-
echo "Error: yq is required but not installed. Install with: brew install yq" >&2
|
|
251
|
-
return 1
|
|
252
|
-
fi
|
|
253
|
-
return 0
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
# get_sprint_metadata FIELD
|
|
257
|
-
# Extract a field from sprint metadata
|
|
258
|
-
# Arguments:
|
|
259
|
-
# FIELD - Field name (number, name, goal, start_date, end_date, status)
|
|
260
|
-
# Returns: field value or empty string
|
|
261
|
-
get_sprint_metadata() {
|
|
262
|
-
local field="$1"
|
|
263
|
-
local sprint_file
|
|
264
|
-
sprint_file=$(get_sprint_file)
|
|
265
|
-
|
|
266
|
-
if [[ ! -f "$sprint_file" ]]; then
|
|
267
|
-
return 1
|
|
268
|
-
fi
|
|
269
|
-
|
|
270
|
-
check_yq || return 1
|
|
271
|
-
yq eval ".sprint.$field // \"\"" "$sprint_file" 2>/dev/null
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
# get_sprint_summary
|
|
275
|
-
# Get one-line sprint summary: "Sprint N: Goal"
|
|
276
|
-
# Returns: formatted summary string
|
|
277
|
-
get_sprint_summary() {
|
|
278
|
-
local sprint_num sprint_goal
|
|
279
|
-
sprint_num=$(get_sprint_metadata "number")
|
|
280
|
-
sprint_goal=$(get_sprint_metadata "goal")
|
|
281
|
-
|
|
282
|
-
if [[ -n "$sprint_num" && "$sprint_num" != "null" ]]; then
|
|
283
|
-
echo "Sprint ${sprint_num}: ${sprint_goal}"
|
|
284
|
-
fi
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
# sum_points VALUES
|
|
288
|
-
# Sum a list of point values (one per line)
|
|
289
|
-
# Arguments:
|
|
290
|
-
# VALUES - newline-separated point values from yq
|
|
291
|
-
# Returns: integer sum
|
|
292
|
-
sum_points() {
|
|
293
|
-
local result
|
|
294
|
-
result=$(echo "$1" | paste -sd+ - | bc 2>/dev/null)
|
|
295
|
-
echo "${result:-0}"
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
# get_sprint_progress
|
|
299
|
-
# Get sprint progress as "completed/total points"
|
|
300
|
-
# Returns: formatted progress string
|
|
301
|
-
get_sprint_progress() {
|
|
302
|
-
local sprint_file
|
|
303
|
-
sprint_file=$(get_sprint_file)
|
|
304
|
-
|
|
305
|
-
if [[ ! -f "$sprint_file" ]]; then
|
|
306
|
-
return 1
|
|
307
|
-
fi
|
|
308
|
-
|
|
309
|
-
check_yq || return 1
|
|
310
|
-
|
|
311
|
-
# Get summary fields if available
|
|
312
|
-
local completed total
|
|
313
|
-
completed=$(yq '.summary.completed_points // 0' "$sprint_file" 2>/dev/null)
|
|
314
|
-
total=$(yq '.summary.total_points // 0' "$sprint_file" 2>/dev/null)
|
|
315
|
-
|
|
316
|
-
# If summary not available, calculate from stories
|
|
317
|
-
if [[ "$total" == "0" || "$total" == "null" ]]; then
|
|
318
|
-
total=$(sum_points "$(yq '.epics[].stories[].points' "$sprint_file" 2>/dev/null)")
|
|
319
|
-
fi
|
|
320
|
-
|
|
321
|
-
echo "Progress: ${completed:-0}/${total:-0} points"
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
# get_story_counts
|
|
325
|
-
# Get story counts by status
|
|
326
|
-
# Returns: "backlog:N in_progress:N done:N" format
|
|
327
|
-
get_story_counts() {
|
|
328
|
-
local sprint_file
|
|
329
|
-
sprint_file=$(get_sprint_file)
|
|
330
|
-
|
|
331
|
-
if [[ ! -f "$sprint_file" ]]; then
|
|
332
|
-
return 1
|
|
333
|
-
fi
|
|
334
|
-
|
|
335
|
-
check_yq || return 1
|
|
336
|
-
|
|
337
|
-
local backlog in_progress done
|
|
338
|
-
backlog=$(yq eval '[.epics[].stories[] | select(.status == "backlog")] | length' "$sprint_file" 2>/dev/null)
|
|
339
|
-
in_progress=$(yq eval '[.epics[].stories[] | select(.status == "in_progress")] | length' "$sprint_file" 2>/dev/null)
|
|
340
|
-
done=$(yq eval '[.epics[].stories[] | select(.status == "done")] | length' "$sprint_file" 2>/dev/null)
|
|
341
|
-
|
|
342
|
-
echo "backlog:${backlog:-0} in_progress:${in_progress:-0} done:${done:-0}"
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
# get_point_counts
|
|
346
|
-
# Get point totals by status
|
|
347
|
-
# Returns: "backlog:N in_progress:N done:N total:N" format
|
|
348
|
-
get_point_counts() {
|
|
349
|
-
local sprint_file
|
|
350
|
-
sprint_file=$(get_sprint_file)
|
|
351
|
-
|
|
352
|
-
if [[ ! -f "$sprint_file" ]]; then
|
|
353
|
-
return 1
|
|
354
|
-
fi
|
|
355
|
-
|
|
356
|
-
check_yq || return 1
|
|
357
|
-
|
|
358
|
-
local backlog in_progress total
|
|
359
|
-
total=$(sum_points "$(yq '.epics[].stories[].points' "$sprint_file" 2>/dev/null)")
|
|
360
|
-
backlog=$(sum_points "$(yq '.epics[].stories[] | select(.status == "backlog") | .points' "$sprint_file" 2>/dev/null)")
|
|
361
|
-
in_progress=$(sum_points "$(yq '.epics[].stories[] | select(.status == "in_progress") | .points' "$sprint_file" 2>/dev/null)")
|
|
362
|
-
|
|
363
|
-
echo "backlog:${backlog:-0} in_progress:${in_progress:-0} total:${total:-0}"
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
# =============================================================================
|
|
367
|
-
# Drift Detection Functions
|
|
368
|
-
# =============================================================================
|
|
369
|
-
|
|
370
|
-
# reconcile_drift STORY_ID
|
|
371
|
-
# Auto-reconcile a drifted story by updating YAML status to done and Jira to Done
|
|
372
|
-
# Logs the reconciliation event
|
|
373
|
-
# Arguments:
|
|
374
|
-
# STORY_ID - Story identifier (e.g., "8-1")
|
|
375
|
-
reconcile_drift() {
|
|
376
|
-
local story_id="$1"
|
|
377
|
-
|
|
378
|
-
if [[ -z "$story_id" ]]; then
|
|
379
|
-
echo "Error: story_id required" >&2
|
|
380
|
-
return 1
|
|
381
|
-
fi
|
|
382
|
-
|
|
383
|
-
local yaml_updated=false
|
|
384
|
-
local jira_updated=false
|
|
385
|
-
local messages=()
|
|
386
|
-
|
|
387
|
-
# Update YAML status to done
|
|
388
|
-
update_story_status "$story_id" "done"
|
|
389
|
-
if [[ $? -eq 0 ]]; then
|
|
390
|
-
yaml_updated=true
|
|
391
|
-
messages+=("YAML status updated to done")
|
|
392
|
-
fi
|
|
393
|
-
|
|
394
|
-
# Get Jira key and transition to Done
|
|
395
|
-
local jira_key
|
|
396
|
-
jira_key=$(get_story_field "$story_id" "jira")
|
|
397
|
-
|
|
398
|
-
if [[ -n "$jira_key" && "$jira_key" != "null" ]]; then
|
|
399
|
-
# Try to transition Jira to Done
|
|
400
|
-
if jira issue move "$jira_key" "Done" 2>/dev/null; then
|
|
401
|
-
jira_updated=true
|
|
402
|
-
messages+=("Jira $jira_key transitioned to Done")
|
|
403
|
-
else
|
|
404
|
-
messages+=("Jira $jira_key transition failed (may need manual update)")
|
|
405
|
-
fi
|
|
406
|
-
fi
|
|
407
|
-
|
|
408
|
-
# Log the reconciliation event
|
|
409
|
-
if [[ "$yaml_updated" == "true" || "$jira_updated" == "true" ]]; then
|
|
410
|
-
log_reconciliation "$story_id" "Auto-reconciled: ${messages[*]}"
|
|
411
|
-
return 0
|
|
412
|
-
else
|
|
413
|
-
return 1
|
|
414
|
-
fi
|
|
415
|
-
}
|