@pennyfarthing/core 8.1.0 → 9.0.3
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 +18 -9
- package/package.json +3 -3
- package/packages/core/dist/cli/commands/doctor.d.ts +5 -2
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +225 -17
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +3 -246
- 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 +4 -140
- package/packages/core/dist/cli/commands/update.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 +2 -0
- package/packages/core/dist/cli/utils/constants.js.map +1 -1
- package/packages/core/dist/cli/utils/settings.d.ts +22 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/settings.js +300 -0
- package/packages/core/dist/cli/utils/settings.js.map +1 -0
- package/pennyfarthing-dist/agents/README.md +1 -1
- package/pennyfarthing-dist/agents/dev.md +1 -1
- package/pennyfarthing-dist/agents/handoff.md +1 -1
- package/pennyfarthing-dist/agents/reviewer-preflight.md +1 -1
- package/pennyfarthing-dist/agents/sm-setup.md +3 -3
- package/pennyfarthing-dist/agents/sm.md +1 -1
- package/pennyfarthing-dist/agents/tea.md +1 -1
- package/pennyfarthing-dist/agents/testing-runner.md +3 -3
- package/pennyfarthing-dist/commands/architect.md +2 -0
- package/pennyfarthing-dist/commands/chore.md +18 -17
- package/pennyfarthing-dist/commands/continue-session.md +43 -9
- package/pennyfarthing-dist/commands/dev.md +2 -0
- package/pennyfarthing-dist/commands/devops.md +2 -0
- package/pennyfarthing-dist/commands/fix-blocker.md +22 -0
- package/pennyfarthing-dist/commands/git-cleanup.md +25 -19
- package/pennyfarthing-dist/commands/health-check.md +2 -0
- package/pennyfarthing-dist/commands/new-work.md +23 -0
- package/pennyfarthing-dist/commands/orchestrator.md +2 -0
- package/pennyfarthing-dist/commands/parallel-work.md +4 -2
- package/pennyfarthing-dist/commands/patch.md +210 -0
- package/pennyfarthing-dist/commands/pm.md +2 -0
- package/pennyfarthing-dist/commands/reviewer.md +2 -0
- package/pennyfarthing-dist/commands/sm.md +2 -0
- package/pennyfarthing-dist/commands/tea.md +2 -0
- package/pennyfarthing-dist/commands/tech-writer.md +2 -0
- package/pennyfarthing-dist/commands/ux-designer.md +2 -0
- package/pennyfarthing-dist/commands/work.md +2 -0
- package/pennyfarthing-dist/guides/agent-behavior.md +29 -264
- package/pennyfarthing-dist/guides/session-schema.md +346 -0
- package/pennyfarthing-dist/guides/skill-schema.md +412 -0
- package/pennyfarthing-dist/guides/workflow-step-schema.md +512 -0
- package/pennyfarthing-dist/guides/xml-tags.md +292 -0
- package/pennyfarthing-dist/scripts/core/agent-session.sh +7 -0
- package/pennyfarthing-dist/scripts/core/check-context.sh +140 -226
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +4 -1
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +1 -7
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +43 -8
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +4 -11
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +3 -8
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +3 -3
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +30 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -7
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +2 -8
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +2 -8
- package/pennyfarthing-dist/scripts/lib/find-root.sh +41 -44
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +1 -7
- package/pennyfarthing-dist/scripts/sprint/archive-story.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/available-stories.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/check-story.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/list-future.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +2 -8
- package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +2 -8
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +2 -1
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +4 -9
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +2 -8
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +2 -8
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -7
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +2 -8
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +2 -8
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +2 -8
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +2 -8
- package/pennyfarthing-dist/skills/agentic-patterns/SKILL.md +4 -0
- package/pennyfarthing-dist/skills/changelog/SKILL.md +18 -0
- package/pennyfarthing-dist/skills/code-review/SKILL.md +5 -1
- package/pennyfarthing-dist/skills/context-engineering/SKILL.md +3 -0
- package/pennyfarthing-dist/skills/cyclist/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +25 -1
- package/pennyfarthing-dist/skills/finalize-run/SKILL.md +3 -0
- package/pennyfarthing-dist/skills/jira/SKILL.md +48 -24
- package/pennyfarthing-dist/skills/judge/SKILL.md +8 -0
- package/pennyfarthing-dist/skills/just/SKILL.md +11 -0
- package/pennyfarthing-dist/skills/mermaid/SKILL.md +16 -0
- package/pennyfarthing-dist/skills/otel/skill.md +4 -0
- package/pennyfarthing-dist/skills/permissions/skill.md +3 -0
- package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +9 -0
- package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +7 -0
- package/pennyfarthing-dist/skills/sprint/skill.md +30 -30
- package/pennyfarthing-dist/skills/story/skill.md +16 -16
- package/pennyfarthing-dist/skills/systematic-debugging/SKILL.md +56 -0
- package/pennyfarthing-dist/skills/testing/SKILL.md +22 -0
- package/pennyfarthing-dist/skills/theme/skill.md +12 -0
- package/pennyfarthing-dist/skills/theme-creation/SKILL.md +4 -0
- package/pennyfarthing-dist/skills/workflow/skill.md +22 -14
- package/pennyfarthing-dist/skills/yq/SKILL.md +8 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-01-initialize.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-01b-continue.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-02-context.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-03-patterns.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-04-components.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-05-interfaces.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-06-risks.md +12 -0
- package/pennyfarthing-dist/workflows/architecture/steps/step-07-document.md +12 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-01-validate-prerequisites.md +25 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-02-design-epics.md +23 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-03-create-stories.md +26 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-04-final-validation.md +24 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +23 -0
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +43 -41
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +50 -19
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +102 -111
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +48 -39
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +30 -31
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-01-document-discovery.md +21 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-02-prd-analysis.md +21 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-03-epic-coverage-validation.md +23 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-04-ux-alignment.md +23 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-05-epic-quality-review.md +28 -0
- package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-06-final-assessment.md +25 -0
- package/pennyfarthing-dist/workflows/interactive-debug/steps/step-01-connect.md +257 -0
- package/pennyfarthing-dist/workflows/interactive-debug/steps/step-02-explore.md +107 -0
- package/pennyfarthing-dist/workflows/interactive-debug/steps/step-03-fix.md +127 -0
- package/pennyfarthing-dist/workflows/interactive-debug/steps/step-04-commit.md +122 -0
- package/pennyfarthing-dist/workflows/interactive-debug/workflow.yaml +51 -0
- package/pennyfarthing-dist/workflows/patch.yaml +68 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-01-init.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-01b-continue.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-02-discovery.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-03-success.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-04-journeys.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-05-domain.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-06-innovation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-07-project-type.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-08-scoping.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-09-functional.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-10-nonfunctional.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-11-polish.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-c/step-12-complete.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-01-discovery.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-01b-legacy-conversion.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-02-review.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-03-edit.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-e/step-e-04-complete.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-01-discovery.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-02-format-detection.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-02b-parity-check.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-03-density-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-04-brief-coverage-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-05-measurability-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-06-traceability-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-07-implementation-leakage-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-08-domain-compliance-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-09-project-type-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-10-smart-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-11-holistic-quality-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-12-completeness-validation.md +6 -0
- package/pennyfarthing-dist/workflows/prd/steps-v/step-v-13-report-complete.md +6 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-01-init.md +18 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-01b-continue.md +19 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-02-vision.md +22 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-03-users.md +22 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-04-metrics.md +23 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-05-scope.md +24 -0
- package/pennyfarthing-dist/workflows/product-brief/steps/step-06-complete.md +22 -0
- package/pennyfarthing-dist/workflows/project-context/steps/step-01-discover.md +22 -0
- package/pennyfarthing-dist/workflows/project-context/steps/step-02-generate.md +31 -0
- package/pennyfarthing-dist/workflows/project-context/steps/step-03-complete.md +28 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-01-mode-detection.md +21 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-02-context-gathering.md +23 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-03-execute.md +25 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-04-self-check.md +22 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-05-adversarial-review.md +23 -0
- package/pennyfarthing-dist/workflows/quick-dev/steps/step-06-resolve-findings.md +23 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +12 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +12 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +12 -0
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +12 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-01-init.md +22 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-02-domain-analysis.md +24 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-03-competitive-landscape.md +25 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-04-regulatory-focus.md +26 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-05-technical-trends.md +26 -0
- package/pennyfarthing-dist/workflows/research/steps-domain/step-06-research-synthesis.md +34 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-01-init.md +23 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-02-customer-behavior.md +25 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-02-customer-insights.md +27 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-03-customer-pain-points.md +26 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-04-customer-decisions.md +27 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-05-competitive-analysis.md +26 -0
- package/pennyfarthing-dist/workflows/research/steps-market/step-06-research-completion.md +35 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-01-init.md +22 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-02-technical-overview.md +25 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-03-integration-patterns.md +26 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-04-architectural-patterns.md +26 -0
- package/pennyfarthing-dist/workflows/research/steps-technical/step-05-implementation-research.md +29 -1
- package/pennyfarthing-dist/workflows/research/steps-technical/step-06-research-synthesis.md +37 -1
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-01-parse-epic-files.md +15 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-02-build-sprint-status.md +17 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-03-status-detection.md +16 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-04-generate-status-file.md +17 -0
- package/pennyfarthing-dist/workflows/sprint-planning/steps/step-05-validate-and-report.md +22 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-01-init.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-01b-continue.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-02-discovery.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-03-core-experience.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-04-emotional-response.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-05-inspiration.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-06-design-system.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-07-defining-experience.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-08-visual-foundation.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-09-design-directions.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-10-user-journeys.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-11-component-strategy.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-12-ux-patterns.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-13-responsive-accessibility.md +6 -0
- package/pennyfarthing-dist/workflows/ux-design/steps/step-14-complete.md +6 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/context.py +414 -0
- package/pennyfarthing_scripts/migration/__init__.py +39 -0
- package/pennyfarthing_scripts/migration/__main__.py +10 -0
- package/pennyfarthing_scripts/migration/cli.py +304 -0
- package/pennyfarthing_scripts/migration/session.py +384 -0
- package/pennyfarthing_scripts/migration/skill.py +188 -0
- package/pennyfarthing_scripts/migration/step.py +229 -0
- package/pennyfarthing_scripts/migration/validate.py +282 -0
- package/pennyfarthing_scripts/patch_mode.py +449 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.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__/session.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/cli.py +201 -0
- package/pennyfarthing_scripts/prime/models.py +9 -0
- package/pennyfarthing_scripts/prime/persona.py +41 -0
- package/pennyfarthing_scripts/prime/tiers.py +201 -0
- package/pennyfarthing_scripts/schema_validation_hook.py +306 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive_epic.py +399 -0
- package/pennyfarthing_scripts/sprint/cli.py +100 -0
- package/pennyfarthing_scripts/sprint/import_epic.py +431 -0
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_patch_mode.py +830 -0
- package/pennyfarthing_scripts/tests/test_tiers.py +1090 -0
- package/pennyfarthing_scripts/tests/test_token_counting.py +559 -0
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +0 -10
- package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +0 -270
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.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__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.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_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Migration CLI - Click-based CLI for XML schema migration tools.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
pf migration [COMMAND] [ARGS]...
|
|
6
|
+
python -m pennyfarthing_scripts.migration [COMMAND] [ARGS]...
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
session Migrate session files to XML format
|
|
10
|
+
skill Audit skill files for required tags
|
|
11
|
+
step Audit workflow step files
|
|
12
|
+
validate Validate files against XML schemas
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
|
|
21
|
+
from pennyfarthing_scripts.common.output import error, info, success, warn
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _find_project_root() -> Path:
|
|
25
|
+
"""Find project root by looking for .pennyfarthing directory."""
|
|
26
|
+
cwd = Path.cwd()
|
|
27
|
+
|
|
28
|
+
# Walk up looking for .pennyfarthing
|
|
29
|
+
for parent in [cwd, *cwd.parents]:
|
|
30
|
+
if (parent / ".pennyfarthing").exists():
|
|
31
|
+
return parent
|
|
32
|
+
|
|
33
|
+
# Fall back to cwd
|
|
34
|
+
return cwd
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@click.group()
|
|
38
|
+
def migration():
|
|
39
|
+
"""XML schema migration tools.
|
|
40
|
+
|
|
41
|
+
\b
|
|
42
|
+
Commands:
|
|
43
|
+
session - Migrate session files to XML format
|
|
44
|
+
skill - Audit skill files for required tags
|
|
45
|
+
step - Audit workflow step files
|
|
46
|
+
validate - Validate files against schemas
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@migration.command()
|
|
52
|
+
@click.argument("file", required=False, type=click.Path(exists=True))
|
|
53
|
+
@click.option("--dry-run", is_flag=True, help="Show what would be done without writing")
|
|
54
|
+
@click.option("--all", "convert_all", is_flag=True, help="Convert all session files")
|
|
55
|
+
def session(file: str | None, dry_run: bool, convert_all: bool):
|
|
56
|
+
"""Migrate session files to XML format.
|
|
57
|
+
|
|
58
|
+
\b
|
|
59
|
+
Arguments:
|
|
60
|
+
FILE - Specific session file to convert (optional)
|
|
61
|
+
|
|
62
|
+
\b
|
|
63
|
+
Examples:
|
|
64
|
+
pf migration session --dry-run .session/archive/MSSCI-12142-session.md
|
|
65
|
+
pf migration session --all --dry-run
|
|
66
|
+
pf migration session --all
|
|
67
|
+
"""
|
|
68
|
+
from pennyfarthing_scripts.migration.session import (
|
|
69
|
+
convert_session_file,
|
|
70
|
+
find_session_files,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
root = _find_project_root()
|
|
74
|
+
|
|
75
|
+
if file:
|
|
76
|
+
file_path = Path(file)
|
|
77
|
+
result = convert_session_file(file_path, dry_run=dry_run)
|
|
78
|
+
|
|
79
|
+
if result.get("skipped"):
|
|
80
|
+
info(f"{file_path.name}: Already in XML format")
|
|
81
|
+
elif result.get("success"):
|
|
82
|
+
if dry_run:
|
|
83
|
+
click.echo(f"\n=== Would convert {file_path.name} to: ===\n")
|
|
84
|
+
click.echo(result.get("content"))
|
|
85
|
+
click.echo("\n=== End ===")
|
|
86
|
+
else:
|
|
87
|
+
success(f"Converted: {file_path.name}")
|
|
88
|
+
else:
|
|
89
|
+
error(result.get("message", "Conversion failed"))
|
|
90
|
+
raise click.Abort()
|
|
91
|
+
|
|
92
|
+
elif convert_all:
|
|
93
|
+
files = find_session_files(root)
|
|
94
|
+
if not files:
|
|
95
|
+
warn("No session files found")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
info(f"Found {len(files)} session files")
|
|
99
|
+
converted = 0
|
|
100
|
+
skipped = 0
|
|
101
|
+
|
|
102
|
+
for file_path in files:
|
|
103
|
+
result = convert_session_file(file_path, dry_run=dry_run)
|
|
104
|
+
|
|
105
|
+
if result.get("skipped"):
|
|
106
|
+
skipped += 1
|
|
107
|
+
elif result.get("success"):
|
|
108
|
+
converted += 1
|
|
109
|
+
if dry_run:
|
|
110
|
+
info(f"Would convert: {file_path.name}")
|
|
111
|
+
else:
|
|
112
|
+
success(f"Converted: {file_path.name}")
|
|
113
|
+
else:
|
|
114
|
+
error(f"Failed: {file_path.name} - {result.get('message')}")
|
|
115
|
+
|
|
116
|
+
click.echo("")
|
|
117
|
+
info(f"Summary: {converted} converted, {skipped} already XML")
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
# Show available files
|
|
121
|
+
files = find_session_files(root)
|
|
122
|
+
if not files:
|
|
123
|
+
warn("No session files found")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
info(f"Session files found: {len(files)}")
|
|
127
|
+
for f in files[:10]:
|
|
128
|
+
click.echo(f" {f.relative_to(root)}")
|
|
129
|
+
if len(files) > 10:
|
|
130
|
+
click.echo(f" ... and {len(files) - 10} more")
|
|
131
|
+
click.echo("")
|
|
132
|
+
info("Use --all to convert all, or specify a file path")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@migration.command()
|
|
136
|
+
@click.argument("skill_name", required=False)
|
|
137
|
+
@click.option("--report", is_flag=True, help="Generate detailed report")
|
|
138
|
+
def skill(skill_name: str | None, report: bool):
|
|
139
|
+
"""Audit skill files for required XML tags.
|
|
140
|
+
|
|
141
|
+
\b
|
|
142
|
+
Arguments:
|
|
143
|
+
SKILL_NAME - Specific skill to audit (optional)
|
|
144
|
+
|
|
145
|
+
\b
|
|
146
|
+
Examples:
|
|
147
|
+
pf migration skill --report
|
|
148
|
+
pf migration skill sprint
|
|
149
|
+
"""
|
|
150
|
+
from pennyfarthing_scripts.migration.skill import audit_skills
|
|
151
|
+
|
|
152
|
+
root = _find_project_root()
|
|
153
|
+
results = audit_skills(root, skill_name=skill_name)
|
|
154
|
+
|
|
155
|
+
if results["summary"].get("error"):
|
|
156
|
+
error(results["summary"]["error"])
|
|
157
|
+
raise click.Abort()
|
|
158
|
+
|
|
159
|
+
# Print results
|
|
160
|
+
for result in results["results"]:
|
|
161
|
+
if result.status == "OK":
|
|
162
|
+
success(f"{result.skill_name}: All tags present")
|
|
163
|
+
elif result.status == "PARTIAL":
|
|
164
|
+
warn(f"{result.skill_name}: Missing recommended: {', '.join(result.missing_recommended)}")
|
|
165
|
+
else:
|
|
166
|
+
error(f"{result.skill_name}: Missing required: {', '.join(result.missing_required)}")
|
|
167
|
+
|
|
168
|
+
if report and (result.missing_required or result.missing_recommended):
|
|
169
|
+
click.echo(f" Present: {', '.join(result.present_tags)}")
|
|
170
|
+
|
|
171
|
+
# Print summary
|
|
172
|
+
summary = results["summary"]
|
|
173
|
+
click.echo("")
|
|
174
|
+
info("=== Summary ===")
|
|
175
|
+
click.echo(f" Total: {summary['total']}")
|
|
176
|
+
click.echo(f" Valid: {summary['valid']}")
|
|
177
|
+
click.echo(f" Partial: {summary['partial']}")
|
|
178
|
+
click.echo(f" Needs work: {summary['needs_update']}")
|
|
179
|
+
|
|
180
|
+
if summary["needs_update"] > 0:
|
|
181
|
+
click.echo("")
|
|
182
|
+
info("Skills needing updates:")
|
|
183
|
+
for result in results["results"]:
|
|
184
|
+
if not result.is_valid:
|
|
185
|
+
click.echo(f" - {result.skill_name}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@migration.command()
|
|
189
|
+
@click.argument("workflow_name", required=False)
|
|
190
|
+
@click.option("--report", is_flag=True, help="Generate detailed report")
|
|
191
|
+
def step(workflow_name: str | None, report: bool):
|
|
192
|
+
"""Audit workflow step files for required XML tags.
|
|
193
|
+
|
|
194
|
+
\b
|
|
195
|
+
Arguments:
|
|
196
|
+
WORKFLOW_NAME - Specific workflow to audit (optional)
|
|
197
|
+
|
|
198
|
+
\b
|
|
199
|
+
Examples:
|
|
200
|
+
pf migration step --report
|
|
201
|
+
pf migration step architecture
|
|
202
|
+
"""
|
|
203
|
+
from pennyfarthing_scripts.migration.step import audit_workflow_steps
|
|
204
|
+
|
|
205
|
+
root = _find_project_root()
|
|
206
|
+
results = audit_workflow_steps(root, workflow_name=workflow_name)
|
|
207
|
+
|
|
208
|
+
# Print results by workflow
|
|
209
|
+
for workflow in results["workflows"]:
|
|
210
|
+
info(f"Workflow: {workflow.workflow_name} ({workflow.total_steps} steps)")
|
|
211
|
+
|
|
212
|
+
for result in workflow.step_results:
|
|
213
|
+
if result.status == "OK":
|
|
214
|
+
if report:
|
|
215
|
+
success(f" {result.step_name}: OK")
|
|
216
|
+
elif result.status == "PARTIAL":
|
|
217
|
+
warn(f" {result.step_name}: Missing recommended: {', '.join(result.missing_recommended)}")
|
|
218
|
+
else:
|
|
219
|
+
error(f" {result.step_name}: Missing required: {', '.join(result.missing_required)}")
|
|
220
|
+
|
|
221
|
+
# Print summary
|
|
222
|
+
summary = results["summary"]
|
|
223
|
+
click.echo("")
|
|
224
|
+
info("=== Summary ===")
|
|
225
|
+
click.echo(f" Workflows: {summary['total_workflows']}")
|
|
226
|
+
click.echo(f" Step files: {summary['total_files']}")
|
|
227
|
+
click.echo(f" Valid: {summary['valid']}")
|
|
228
|
+
click.echo(f" Needs work: {summary['needs_update']}")
|
|
229
|
+
|
|
230
|
+
if summary["needs_update"] > 0:
|
|
231
|
+
click.echo("")
|
|
232
|
+
info("Workflows with missing required tags:")
|
|
233
|
+
for workflow in results["workflows"]:
|
|
234
|
+
if workflow.needs_update > 0:
|
|
235
|
+
click.echo(f" - {workflow.workflow_name} ({workflow.needs_update} files)")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@migration.command()
|
|
239
|
+
@click.option(
|
|
240
|
+
"--type",
|
|
241
|
+
"file_type",
|
|
242
|
+
type=click.Choice(["session", "skill", "step", "all"]),
|
|
243
|
+
default="all",
|
|
244
|
+
help="Type of files to validate",
|
|
245
|
+
)
|
|
246
|
+
@click.option("--strict", is_flag=True, help="Treat warnings as errors")
|
|
247
|
+
def validate(file_type: str, strict: bool):
|
|
248
|
+
"""Validate files against XML schemas.
|
|
249
|
+
|
|
250
|
+
\b
|
|
251
|
+
Examples:
|
|
252
|
+
pf migration validate
|
|
253
|
+
pf migration validate --type skill
|
|
254
|
+
pf migration validate --strict
|
|
255
|
+
"""
|
|
256
|
+
from pennyfarthing_scripts.migration.validate import validate_all
|
|
257
|
+
|
|
258
|
+
root = _find_project_root()
|
|
259
|
+
summary = validate_all(root, file_type=file_type, strict=strict)
|
|
260
|
+
|
|
261
|
+
# Print results
|
|
262
|
+
for result in summary.results:
|
|
263
|
+
rel_path = result.file_path.relative_to(root) if root in result.file_path.parents else result.file_path
|
|
264
|
+
if result.status == "PASS":
|
|
265
|
+
success(f"{rel_path}")
|
|
266
|
+
elif result.status == "WARN":
|
|
267
|
+
warn(f"{rel_path}")
|
|
268
|
+
for w in result.warnings:
|
|
269
|
+
click.echo(f" - {w}")
|
|
270
|
+
else:
|
|
271
|
+
error(f"{rel_path}")
|
|
272
|
+
for e in result.errors:
|
|
273
|
+
click.echo(f" - {e}")
|
|
274
|
+
|
|
275
|
+
# Print summary
|
|
276
|
+
click.echo("")
|
|
277
|
+
info("=== Summary ===")
|
|
278
|
+
click.echo(f" Passed: {summary.passed}")
|
|
279
|
+
click.echo(f" Warnings: {summary.warnings}")
|
|
280
|
+
click.echo(f" Errors: {summary.errors}")
|
|
281
|
+
|
|
282
|
+
if not summary.success:
|
|
283
|
+
raise click.Abort()
|
|
284
|
+
|
|
285
|
+
if strict and summary.warnings > 0:
|
|
286
|
+
error("Warnings treated as errors in strict mode")
|
|
287
|
+
raise click.Abort()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# Alias for backwards compatibility
|
|
291
|
+
cli = migration
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def main(args: list[str] | None = None) -> int:
|
|
295
|
+
"""Entry point."""
|
|
296
|
+
try:
|
|
297
|
+
migration(args)
|
|
298
|
+
return 0
|
|
299
|
+
except SystemExit as e:
|
|
300
|
+
return e.code if isinstance(e.code, int) else 0
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
migration()
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session file migration tools.
|
|
3
|
+
|
|
4
|
+
Converts session files from markdown format to XML format
|
|
5
|
+
per guides/session-schema.md.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import date
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class AcceptanceCriterion:
|
|
19
|
+
"""An acceptance criterion."""
|
|
20
|
+
|
|
21
|
+
id: int
|
|
22
|
+
description: str
|
|
23
|
+
status: Literal["pending", "in-progress", "done", "blocked"] = "pending"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class WorkLogEntry:
|
|
28
|
+
"""A work log entry."""
|
|
29
|
+
|
|
30
|
+
agent: str
|
|
31
|
+
date: str
|
|
32
|
+
content: str
|
|
33
|
+
phase: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ReviewAssessment:
|
|
38
|
+
"""A reviewer assessment."""
|
|
39
|
+
|
|
40
|
+
agent: str = "reviewer"
|
|
41
|
+
verdict: Literal["approved", "rejected", "needs-work"] = "approved"
|
|
42
|
+
content: str = ""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class SessionFile:
|
|
47
|
+
"""Parsed session file data."""
|
|
48
|
+
|
|
49
|
+
story_id: str
|
|
50
|
+
workflow: str = "tdd"
|
|
51
|
+
jira: str = ""
|
|
52
|
+
epic: str = ""
|
|
53
|
+
points: int = 0
|
|
54
|
+
started: str = ""
|
|
55
|
+
phase: str = "setup"
|
|
56
|
+
next_agent: str = "sm"
|
|
57
|
+
handoff_ready: bool = False
|
|
58
|
+
acceptance_criteria: list[AcceptanceCriterion] = field(default_factory=list)
|
|
59
|
+
context: str = ""
|
|
60
|
+
work_log: list[WorkLogEntry] = field(default_factory=list)
|
|
61
|
+
assessment: ReviewAssessment | None = None
|
|
62
|
+
|
|
63
|
+
def to_xml(self) -> str:
|
|
64
|
+
"""Convert to XML format."""
|
|
65
|
+
lines = [f'<session story="{self.story_id}" workflow="{self.workflow}">']
|
|
66
|
+
|
|
67
|
+
# Meta section
|
|
68
|
+
lines.append(" <meta>")
|
|
69
|
+
lines.append(f" <jira>{self.jira or self.story_id}</jira>")
|
|
70
|
+
if self.epic:
|
|
71
|
+
lines.append(f" <epic>{self.epic}</epic>")
|
|
72
|
+
if self.points:
|
|
73
|
+
lines.append(f" <points>{self.points}</points>")
|
|
74
|
+
lines.append(f" <started>{self.started or date.today().isoformat()}</started>")
|
|
75
|
+
lines.append(" </meta>")
|
|
76
|
+
lines.append("")
|
|
77
|
+
|
|
78
|
+
# Status
|
|
79
|
+
handoff = "true" if self.handoff_ready else "false"
|
|
80
|
+
lines.append(
|
|
81
|
+
f' <status phase="{self.phase}" next-agent="{self.next_agent}" '
|
|
82
|
+
f'handoff-ready="{handoff}"/>'
|
|
83
|
+
)
|
|
84
|
+
lines.append("")
|
|
85
|
+
|
|
86
|
+
# Acceptance criteria
|
|
87
|
+
lines.append(" <acceptance-criteria>")
|
|
88
|
+
for ac in self.acceptance_criteria:
|
|
89
|
+
lines.append(f' <ac id="{ac.id}" status="{ac.status}">{ac.description}</ac>')
|
|
90
|
+
lines.append(" </acceptance-criteria>")
|
|
91
|
+
lines.append("")
|
|
92
|
+
|
|
93
|
+
# Context
|
|
94
|
+
lines.append(" <context>")
|
|
95
|
+
if self.context:
|
|
96
|
+
for line in self.context.strip().split("\n"):
|
|
97
|
+
lines.append(f" {line}")
|
|
98
|
+
else:
|
|
99
|
+
lines.append(f" See: .session/context-story-{self.story_id}.md")
|
|
100
|
+
lines.append(" </context>")
|
|
101
|
+
lines.append("")
|
|
102
|
+
|
|
103
|
+
# Work log
|
|
104
|
+
lines.append(" <work-log>")
|
|
105
|
+
for entry in self.work_log:
|
|
106
|
+
phase_attr = f' phase="{entry.phase}"' if entry.phase else ""
|
|
107
|
+
lines.append(f' <entry agent="{entry.agent}" date="{entry.date}"{phase_attr}>')
|
|
108
|
+
for line in entry.content.strip().split("\n"):
|
|
109
|
+
lines.append(f" {line}")
|
|
110
|
+
lines.append(" </entry>")
|
|
111
|
+
|
|
112
|
+
# Assessment (if present)
|
|
113
|
+
if self.assessment:
|
|
114
|
+
lines.append(
|
|
115
|
+
f' <assessment agent="{self.assessment.agent}" '
|
|
116
|
+
f'verdict="{self.assessment.verdict}">'
|
|
117
|
+
)
|
|
118
|
+
for line in self.assessment.content.strip().split("\n"):
|
|
119
|
+
lines.append(f" {line}")
|
|
120
|
+
lines.append(" </assessment>")
|
|
121
|
+
|
|
122
|
+
lines.append(" </work-log>")
|
|
123
|
+
lines.append("</session>")
|
|
124
|
+
|
|
125
|
+
return "\n".join(lines)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _extract_field(content: str, label: str) -> str:
|
|
129
|
+
"""Extract a field value from markdown **Label:** value format."""
|
|
130
|
+
pattern = rf"\*\*{re.escape(label)}:\*\*\s*(.+?)(?:\n|$)"
|
|
131
|
+
match = re.search(pattern, content)
|
|
132
|
+
if match:
|
|
133
|
+
return match.group(1).strip()
|
|
134
|
+
return ""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _normalize_phase(phase: str) -> str:
|
|
138
|
+
"""Normalize phase value."""
|
|
139
|
+
phase = phase.lower()
|
|
140
|
+
# Remove suffixes
|
|
141
|
+
phase = re.sub(r"-complete$", "", phase)
|
|
142
|
+
phase = re.sub(r"^dev-", "", phase)
|
|
143
|
+
phase = re.sub(r"^review-", "", phase)
|
|
144
|
+
# Map common values
|
|
145
|
+
if phase in ("approved", "finished"):
|
|
146
|
+
phase = "finish"
|
|
147
|
+
elif phase in ("implementing", "implementation"):
|
|
148
|
+
phase = "green"
|
|
149
|
+
return phase
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _normalize_agent(agent: str) -> str:
|
|
153
|
+
"""Normalize agent name."""
|
|
154
|
+
agent = agent.lower()
|
|
155
|
+
# Remove parenthetical persona names
|
|
156
|
+
agent = re.sub(r"\s*\([^)]+\)", "", agent)
|
|
157
|
+
# Take first word only
|
|
158
|
+
agent = agent.split()[0] if agent else "sm"
|
|
159
|
+
return agent
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _parse_acceptance_criteria(content: str) -> list[AcceptanceCriterion]:
|
|
163
|
+
"""Parse acceptance criteria from markdown checkboxes."""
|
|
164
|
+
criteria = []
|
|
165
|
+
|
|
166
|
+
# Find AC section
|
|
167
|
+
ac_section = re.search(
|
|
168
|
+
r"## Acceptance Criteria\n(.+?)(?=\n##|\Z)", content, re.DOTALL
|
|
169
|
+
)
|
|
170
|
+
if not ac_section:
|
|
171
|
+
return criteria
|
|
172
|
+
|
|
173
|
+
section_text = ac_section.group(1)
|
|
174
|
+
|
|
175
|
+
# Pattern 1: - [x] AC1: Description
|
|
176
|
+
pattern1 = re.compile(r"^\s*-\s*\[([xX\s])\]\s*AC(\d+):?\s*(.+)$", re.MULTILINE)
|
|
177
|
+
for match in pattern1.finditer(section_text):
|
|
178
|
+
checked = match.group(1).lower() == "x"
|
|
179
|
+
ac_id = int(match.group(2))
|
|
180
|
+
desc = match.group(3).strip()
|
|
181
|
+
status = "done" if checked else "pending"
|
|
182
|
+
criteria.append(AcceptanceCriterion(id=ac_id, description=desc, status=status))
|
|
183
|
+
|
|
184
|
+
# If no ACs found, try pattern 2: - [x] Description (auto-number)
|
|
185
|
+
if not criteria:
|
|
186
|
+
pattern2 = re.compile(r"^\s*-\s*\[([xX\s])\]\s*(.+)$", re.MULTILINE)
|
|
187
|
+
for i, match in enumerate(pattern2.finditer(section_text), start=1):
|
|
188
|
+
checked = match.group(1).lower() == "x"
|
|
189
|
+
desc = match.group(2).strip()
|
|
190
|
+
status = "done" if checked else "pending"
|
|
191
|
+
criteria.append(AcceptanceCriterion(id=i, description=desc, status=status))
|
|
192
|
+
|
|
193
|
+
return criteria
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _parse_work_log(content: str) -> tuple[list[WorkLogEntry], ReviewAssessment | None]:
|
|
197
|
+
"""Parse work log entries from markdown."""
|
|
198
|
+
entries = []
|
|
199
|
+
assessment = None
|
|
200
|
+
|
|
201
|
+
# Find work log section
|
|
202
|
+
log_section = re.search(r"## Work Log\n(.+?)(?=\n## [^W]|\Z)", content, re.DOTALL)
|
|
203
|
+
if not log_section:
|
|
204
|
+
return entries, assessment
|
|
205
|
+
|
|
206
|
+
section_text = log_section.group(1)
|
|
207
|
+
|
|
208
|
+
# Split by agent headers (### Agent Name (Date) or ### Agent Action (Date))
|
|
209
|
+
header_pattern = re.compile(
|
|
210
|
+
r"^###\s+(\w+)(?:\s+\w+)?\s+\((\d{4}-\d{2}-\d{2})\)", re.MULTILINE
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
matches = list(header_pattern.finditer(section_text))
|
|
214
|
+
|
|
215
|
+
for i, match in enumerate(matches):
|
|
216
|
+
agent = match.group(1).lower()
|
|
217
|
+
entry_date = match.group(2)
|
|
218
|
+
|
|
219
|
+
# Get content until next header or end
|
|
220
|
+
start = match.end()
|
|
221
|
+
end = matches[i + 1].start() if i + 1 < len(matches) else len(section_text)
|
|
222
|
+
entry_content = section_text[start:end].strip()
|
|
223
|
+
|
|
224
|
+
# Determine phase from header context
|
|
225
|
+
phase = None
|
|
226
|
+
header_text = match.group(0).lower()
|
|
227
|
+
if "red" in header_text:
|
|
228
|
+
phase = "red"
|
|
229
|
+
elif "green" in header_text or "implementation" in header_text:
|
|
230
|
+
phase = "green"
|
|
231
|
+
elif "refactor" in header_text:
|
|
232
|
+
phase = "refactor"
|
|
233
|
+
|
|
234
|
+
# Check if this is a reviewer assessment
|
|
235
|
+
if agent == "reviewer" and "verdict" in entry_content.lower():
|
|
236
|
+
verdict = "approved"
|
|
237
|
+
if "rejected" in entry_content.lower():
|
|
238
|
+
verdict = "rejected"
|
|
239
|
+
elif "needs-work" in entry_content.lower() or "needs work" in entry_content.lower():
|
|
240
|
+
verdict = "needs-work"
|
|
241
|
+
assessment = ReviewAssessment(
|
|
242
|
+
agent="reviewer", verdict=verdict, content=entry_content
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
entries.append(
|
|
246
|
+
WorkLogEntry(
|
|
247
|
+
agent=agent, date=entry_date, content=entry_content, phase=phase
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return entries, assessment
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _extract_context(content: str) -> str:
|
|
255
|
+
"""Extract technical context section."""
|
|
256
|
+
# Find context section
|
|
257
|
+
context_section = re.search(
|
|
258
|
+
r"## Technical Context\n(.+?)(?=\n##|\Z)", content, re.DOTALL
|
|
259
|
+
)
|
|
260
|
+
if context_section:
|
|
261
|
+
return context_section.group(1).strip()
|
|
262
|
+
return ""
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def parse_markdown_session(content: str, filename: str) -> SessionFile:
|
|
266
|
+
"""Parse a markdown session file into structured data.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
content: File content
|
|
270
|
+
filename: Filename (used to extract story ID)
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
SessionFile with parsed data
|
|
274
|
+
"""
|
|
275
|
+
# Extract story ID from filename
|
|
276
|
+
story_id = filename.replace("-session.md", "")
|
|
277
|
+
|
|
278
|
+
# Extract fields
|
|
279
|
+
jira = _extract_field(content, "Jira")
|
|
280
|
+
epic = _extract_field(content, "Epic")
|
|
281
|
+
# Clean epic - take just the ID, not description
|
|
282
|
+
if epic:
|
|
283
|
+
epic = epic.split()[0] if " " in epic else epic
|
|
284
|
+
|
|
285
|
+
points_str = _extract_field(content, "Points")
|
|
286
|
+
points = int(points_str) if points_str.isdigit() else 0
|
|
287
|
+
|
|
288
|
+
workflow = _extract_field(content, "Workflow").lower() or "tdd"
|
|
289
|
+
started = _extract_field(content, "Started")
|
|
290
|
+
|
|
291
|
+
# Status fields (may appear twice - get last occurrence)
|
|
292
|
+
all_phases = re.findall(r"\*\*Current Phase:\*\*\s*(.+?)(?:\n|$)", content)
|
|
293
|
+
phase = _normalize_phase(all_phases[-1]) if all_phases else "setup"
|
|
294
|
+
|
|
295
|
+
all_next = re.findall(r"\*\*Next Agent:\*\*\s*(.+?)(?:\n|$)", content)
|
|
296
|
+
next_agent = _normalize_agent(all_next[-1]) if all_next else "sm"
|
|
297
|
+
|
|
298
|
+
all_handoff = re.findall(r"\*\*Handoff Ready:\*\*\s*(.+?)(?:\n|$)", content)
|
|
299
|
+
handoff_ready = bool(all_handoff and all_handoff[-1].lower().startswith("y"))
|
|
300
|
+
|
|
301
|
+
# Parse sections
|
|
302
|
+
acceptance_criteria = _parse_acceptance_criteria(content)
|
|
303
|
+
context = _extract_context(content)
|
|
304
|
+
work_log, assessment = _parse_work_log(content)
|
|
305
|
+
|
|
306
|
+
return SessionFile(
|
|
307
|
+
story_id=story_id,
|
|
308
|
+
workflow=workflow,
|
|
309
|
+
jira=jira,
|
|
310
|
+
epic=epic,
|
|
311
|
+
points=points,
|
|
312
|
+
started=started,
|
|
313
|
+
phase=phase,
|
|
314
|
+
next_agent=next_agent,
|
|
315
|
+
handoff_ready=handoff_ready,
|
|
316
|
+
acceptance_criteria=acceptance_criteria,
|
|
317
|
+
context=context,
|
|
318
|
+
work_log=work_log,
|
|
319
|
+
assessment=assessment,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def is_xml_format(content: str) -> bool:
|
|
324
|
+
"""Check if content is already in XML format."""
|
|
325
|
+
return "<session story=" in content
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def convert_session_file(
|
|
329
|
+
file_path: Path, *, dry_run: bool = False
|
|
330
|
+
) -> dict[str, str | bool]:
|
|
331
|
+
"""Convert a session file from markdown to XML format.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
file_path: Path to session file
|
|
335
|
+
dry_run: If True, return converted content without writing
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Dict with 'success', 'message', and optionally 'content' (for dry_run)
|
|
339
|
+
"""
|
|
340
|
+
if not file_path.exists():
|
|
341
|
+
return {"success": False, "message": f"File not found: {file_path}"}
|
|
342
|
+
|
|
343
|
+
content = file_path.read_text()
|
|
344
|
+
|
|
345
|
+
# Skip if already XML
|
|
346
|
+
if is_xml_format(content):
|
|
347
|
+
return {"success": True, "message": "Already in XML format", "skipped": True}
|
|
348
|
+
|
|
349
|
+
# Parse and convert
|
|
350
|
+
session = parse_markdown_session(content, file_path.name)
|
|
351
|
+
xml_content = session.to_xml()
|
|
352
|
+
|
|
353
|
+
if dry_run:
|
|
354
|
+
return {
|
|
355
|
+
"success": True,
|
|
356
|
+
"message": "Would convert to XML",
|
|
357
|
+
"content": xml_content,
|
|
358
|
+
"dry_run": True,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# Write converted content
|
|
362
|
+
file_path.write_text(xml_content)
|
|
363
|
+
return {"success": True, "message": f"Converted: {file_path.name}"}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def find_session_files(root: Path) -> list[Path]:
|
|
367
|
+
"""Find all session files in a project.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
root: Project root directory
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
List of session file paths
|
|
374
|
+
"""
|
|
375
|
+
session_dir = root / ".session"
|
|
376
|
+
files = []
|
|
377
|
+
|
|
378
|
+
if session_dir.exists():
|
|
379
|
+
files.extend(session_dir.glob("*-session.md"))
|
|
380
|
+
archive_dir = session_dir / "archive"
|
|
381
|
+
if archive_dir.exists():
|
|
382
|
+
files.extend(archive_dir.glob("*-session.md"))
|
|
383
|
+
|
|
384
|
+
return sorted(files)
|