@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.
Files changed (329) hide show
  1. package/README.md +18 -9
  2. package/package.json +3 -3
  3. package/packages/core/dist/cli/commands/doctor.d.ts +5 -2
  4. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/doctor.js +225 -17
  6. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  7. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  8. package/packages/core/dist/cli/commands/init.js +3 -246
  9. package/packages/core/dist/cli/commands/init.js.map +1 -1
  10. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  11. package/packages/core/dist/cli/commands/update.js +4 -140
  12. package/packages/core/dist/cli/commands/update.js.map +1 -1
  13. package/packages/core/dist/cli/utils/constants.d.ts +7 -1
  14. package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
  15. package/packages/core/dist/cli/utils/constants.js +2 -0
  16. package/packages/core/dist/cli/utils/constants.js.map +1 -1
  17. package/packages/core/dist/cli/utils/settings.d.ts +22 -0
  18. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -0
  19. package/packages/core/dist/cli/utils/settings.js +300 -0
  20. package/packages/core/dist/cli/utils/settings.js.map +1 -0
  21. package/pennyfarthing-dist/agents/README.md +1 -1
  22. package/pennyfarthing-dist/agents/dev.md +1 -1
  23. package/pennyfarthing-dist/agents/handoff.md +1 -1
  24. package/pennyfarthing-dist/agents/reviewer-preflight.md +1 -1
  25. package/pennyfarthing-dist/agents/sm-setup.md +3 -3
  26. package/pennyfarthing-dist/agents/sm.md +1 -1
  27. package/pennyfarthing-dist/agents/tea.md +1 -1
  28. package/pennyfarthing-dist/agents/testing-runner.md +3 -3
  29. package/pennyfarthing-dist/commands/architect.md +2 -0
  30. package/pennyfarthing-dist/commands/chore.md +18 -17
  31. package/pennyfarthing-dist/commands/continue-session.md +43 -9
  32. package/pennyfarthing-dist/commands/dev.md +2 -0
  33. package/pennyfarthing-dist/commands/devops.md +2 -0
  34. package/pennyfarthing-dist/commands/fix-blocker.md +22 -0
  35. package/pennyfarthing-dist/commands/git-cleanup.md +25 -19
  36. package/pennyfarthing-dist/commands/health-check.md +2 -0
  37. package/pennyfarthing-dist/commands/new-work.md +23 -0
  38. package/pennyfarthing-dist/commands/orchestrator.md +2 -0
  39. package/pennyfarthing-dist/commands/parallel-work.md +4 -2
  40. package/pennyfarthing-dist/commands/patch.md +210 -0
  41. package/pennyfarthing-dist/commands/pm.md +2 -0
  42. package/pennyfarthing-dist/commands/reviewer.md +2 -0
  43. package/pennyfarthing-dist/commands/sm.md +2 -0
  44. package/pennyfarthing-dist/commands/tea.md +2 -0
  45. package/pennyfarthing-dist/commands/tech-writer.md +2 -0
  46. package/pennyfarthing-dist/commands/ux-designer.md +2 -0
  47. package/pennyfarthing-dist/commands/work.md +2 -0
  48. package/pennyfarthing-dist/guides/agent-behavior.md +29 -264
  49. package/pennyfarthing-dist/guides/session-schema.md +346 -0
  50. package/pennyfarthing-dist/guides/skill-schema.md +412 -0
  51. package/pennyfarthing-dist/guides/workflow-step-schema.md +512 -0
  52. package/pennyfarthing-dist/guides/xml-tags.md +292 -0
  53. package/pennyfarthing-dist/scripts/core/agent-session.sh +7 -0
  54. package/pennyfarthing-dist/scripts/core/check-context.sh +140 -226
  55. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +13 -2
  56. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +4 -1
  57. package/pennyfarthing-dist/scripts/health/drift-detection.sh +1 -7
  58. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +43 -8
  59. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +4 -11
  60. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +3 -8
  61. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +3 -3
  62. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +30 -0
  63. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -7
  64. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +2 -8
  65. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +2 -8
  66. package/pennyfarthing-dist/scripts/lib/find-root.sh +41 -44
  67. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +1 -7
  68. package/pennyfarthing-dist/scripts/sprint/archive-story.sh +2 -8
  69. package/pennyfarthing-dist/scripts/sprint/available-stories.sh +2 -8
  70. package/pennyfarthing-dist/scripts/sprint/check-story.sh +2 -8
  71. package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +2 -8
  72. package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +2 -8
  73. package/pennyfarthing-dist/scripts/sprint/list-future.sh +2 -8
  74. package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +2 -8
  75. package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +2 -8
  76. package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +2 -8
  77. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +2 -1
  78. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +4 -9
  79. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +2 -8
  80. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +2 -8
  81. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -7
  82. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +2 -8
  83. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +2 -8
  84. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +2 -8
  85. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +2 -8
  86. package/pennyfarthing-dist/skills/agentic-patterns/SKILL.md +4 -0
  87. package/pennyfarthing-dist/skills/changelog/SKILL.md +18 -0
  88. package/pennyfarthing-dist/skills/code-review/SKILL.md +5 -1
  89. package/pennyfarthing-dist/skills/context-engineering/SKILL.md +3 -0
  90. package/pennyfarthing-dist/skills/cyclist/SKILL.md +2 -2
  91. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +25 -1
  92. package/pennyfarthing-dist/skills/finalize-run/SKILL.md +3 -0
  93. package/pennyfarthing-dist/skills/jira/SKILL.md +48 -24
  94. package/pennyfarthing-dist/skills/judge/SKILL.md +8 -0
  95. package/pennyfarthing-dist/skills/just/SKILL.md +11 -0
  96. package/pennyfarthing-dist/skills/mermaid/SKILL.md +16 -0
  97. package/pennyfarthing-dist/skills/otel/skill.md +4 -0
  98. package/pennyfarthing-dist/skills/permissions/skill.md +3 -0
  99. package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +9 -0
  100. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +7 -0
  101. package/pennyfarthing-dist/skills/sprint/skill.md +30 -30
  102. package/pennyfarthing-dist/skills/story/skill.md +16 -16
  103. package/pennyfarthing-dist/skills/systematic-debugging/SKILL.md +56 -0
  104. package/pennyfarthing-dist/skills/testing/SKILL.md +22 -0
  105. package/pennyfarthing-dist/skills/theme/skill.md +12 -0
  106. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +4 -0
  107. package/pennyfarthing-dist/skills/workflow/skill.md +22 -14
  108. package/pennyfarthing-dist/skills/yq/SKILL.md +8 -0
  109. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  110. package/pennyfarthing-dist/workflows/architecture/steps/step-01-initialize.md +12 -0
  111. package/pennyfarthing-dist/workflows/architecture/steps/step-01b-continue.md +12 -0
  112. package/pennyfarthing-dist/workflows/architecture/steps/step-02-context.md +12 -0
  113. package/pennyfarthing-dist/workflows/architecture/steps/step-03-patterns.md +12 -0
  114. package/pennyfarthing-dist/workflows/architecture/steps/step-04-components.md +12 -0
  115. package/pennyfarthing-dist/workflows/architecture/steps/step-05-interfaces.md +12 -0
  116. package/pennyfarthing-dist/workflows/architecture/steps/step-06-risks.md +12 -0
  117. package/pennyfarthing-dist/workflows/architecture/steps/step-07-document.md +12 -0
  118. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-01-validate-prerequisites.md +25 -0
  119. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-02-design-epics.md +23 -0
  120. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-03-create-stories.md +26 -0
  121. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-04-final-validation.md +24 -0
  122. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +23 -0
  123. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +43 -41
  124. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-02-categorize.md +50 -19
  125. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +102 -111
  126. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +48 -39
  127. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +30 -31
  128. package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-01-document-discovery.md +21 -0
  129. package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-02-prd-analysis.md +21 -0
  130. package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-03-epic-coverage-validation.md +23 -0
  131. package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-04-ux-alignment.md +23 -0
  132. package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-05-epic-quality-review.md +28 -0
  133. package/pennyfarthing-dist/workflows/implementation-readiness/steps/step-06-final-assessment.md +25 -0
  134. package/pennyfarthing-dist/workflows/interactive-debug/steps/step-01-connect.md +257 -0
  135. package/pennyfarthing-dist/workflows/interactive-debug/steps/step-02-explore.md +107 -0
  136. package/pennyfarthing-dist/workflows/interactive-debug/steps/step-03-fix.md +127 -0
  137. package/pennyfarthing-dist/workflows/interactive-debug/steps/step-04-commit.md +122 -0
  138. package/pennyfarthing-dist/workflows/interactive-debug/workflow.yaml +51 -0
  139. package/pennyfarthing-dist/workflows/patch.yaml +68 -0
  140. package/pennyfarthing-dist/workflows/prd/steps-c/step-01-init.md +6 -0
  141. package/pennyfarthing-dist/workflows/prd/steps-c/step-01b-continue.md +6 -0
  142. package/pennyfarthing-dist/workflows/prd/steps-c/step-02-discovery.md +6 -0
  143. package/pennyfarthing-dist/workflows/prd/steps-c/step-03-success.md +6 -0
  144. package/pennyfarthing-dist/workflows/prd/steps-c/step-04-journeys.md +6 -0
  145. package/pennyfarthing-dist/workflows/prd/steps-c/step-05-domain.md +6 -0
  146. package/pennyfarthing-dist/workflows/prd/steps-c/step-06-innovation.md +6 -0
  147. package/pennyfarthing-dist/workflows/prd/steps-c/step-07-project-type.md +6 -0
  148. package/pennyfarthing-dist/workflows/prd/steps-c/step-08-scoping.md +6 -0
  149. package/pennyfarthing-dist/workflows/prd/steps-c/step-09-functional.md +6 -0
  150. package/pennyfarthing-dist/workflows/prd/steps-c/step-10-nonfunctional.md +6 -0
  151. package/pennyfarthing-dist/workflows/prd/steps-c/step-11-polish.md +6 -0
  152. package/pennyfarthing-dist/workflows/prd/steps-c/step-12-complete.md +6 -0
  153. package/pennyfarthing-dist/workflows/prd/steps-e/step-e-01-discovery.md +6 -0
  154. package/pennyfarthing-dist/workflows/prd/steps-e/step-e-01b-legacy-conversion.md +6 -0
  155. package/pennyfarthing-dist/workflows/prd/steps-e/step-e-02-review.md +6 -0
  156. package/pennyfarthing-dist/workflows/prd/steps-e/step-e-03-edit.md +6 -0
  157. package/pennyfarthing-dist/workflows/prd/steps-e/step-e-04-complete.md +6 -0
  158. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-01-discovery.md +6 -0
  159. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-02-format-detection.md +6 -0
  160. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-02b-parity-check.md +6 -0
  161. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-03-density-validation.md +6 -0
  162. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-04-brief-coverage-validation.md +6 -0
  163. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-05-measurability-validation.md +6 -0
  164. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-06-traceability-validation.md +6 -0
  165. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-07-implementation-leakage-validation.md +6 -0
  166. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-08-domain-compliance-validation.md +6 -0
  167. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-09-project-type-validation.md +6 -0
  168. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-10-smart-validation.md +6 -0
  169. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-11-holistic-quality-validation.md +6 -0
  170. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-12-completeness-validation.md +6 -0
  171. package/pennyfarthing-dist/workflows/prd/steps-v/step-v-13-report-complete.md +6 -0
  172. package/pennyfarthing-dist/workflows/product-brief/steps/step-01-init.md +18 -0
  173. package/pennyfarthing-dist/workflows/product-brief/steps/step-01b-continue.md +19 -0
  174. package/pennyfarthing-dist/workflows/product-brief/steps/step-02-vision.md +22 -0
  175. package/pennyfarthing-dist/workflows/product-brief/steps/step-03-users.md +22 -0
  176. package/pennyfarthing-dist/workflows/product-brief/steps/step-04-metrics.md +23 -0
  177. package/pennyfarthing-dist/workflows/product-brief/steps/step-05-scope.md +24 -0
  178. package/pennyfarthing-dist/workflows/product-brief/steps/step-06-complete.md +22 -0
  179. package/pennyfarthing-dist/workflows/project-context/steps/step-01-discover.md +22 -0
  180. package/pennyfarthing-dist/workflows/project-context/steps/step-02-generate.md +31 -0
  181. package/pennyfarthing-dist/workflows/project-context/steps/step-03-complete.md +28 -0
  182. package/pennyfarthing-dist/workflows/quick-dev/steps/step-01-mode-detection.md +21 -0
  183. package/pennyfarthing-dist/workflows/quick-dev/steps/step-02-context-gathering.md +23 -0
  184. package/pennyfarthing-dist/workflows/quick-dev/steps/step-03-execute.md +25 -0
  185. package/pennyfarthing-dist/workflows/quick-dev/steps/step-04-self-check.md +22 -0
  186. package/pennyfarthing-dist/workflows/quick-dev/steps/step-05-adversarial-review.md +23 -0
  187. package/pennyfarthing-dist/workflows/quick-dev/steps/step-06-resolve-findings.md +23 -0
  188. package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +12 -0
  189. package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +12 -0
  190. package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +12 -0
  191. package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +12 -0
  192. package/pennyfarthing-dist/workflows/research/steps-domain/step-01-init.md +22 -0
  193. package/pennyfarthing-dist/workflows/research/steps-domain/step-02-domain-analysis.md +24 -0
  194. package/pennyfarthing-dist/workflows/research/steps-domain/step-03-competitive-landscape.md +25 -0
  195. package/pennyfarthing-dist/workflows/research/steps-domain/step-04-regulatory-focus.md +26 -0
  196. package/pennyfarthing-dist/workflows/research/steps-domain/step-05-technical-trends.md +26 -0
  197. package/pennyfarthing-dist/workflows/research/steps-domain/step-06-research-synthesis.md +34 -0
  198. package/pennyfarthing-dist/workflows/research/steps-market/step-01-init.md +23 -0
  199. package/pennyfarthing-dist/workflows/research/steps-market/step-02-customer-behavior.md +25 -0
  200. package/pennyfarthing-dist/workflows/research/steps-market/step-02-customer-insights.md +27 -0
  201. package/pennyfarthing-dist/workflows/research/steps-market/step-03-customer-pain-points.md +26 -0
  202. package/pennyfarthing-dist/workflows/research/steps-market/step-04-customer-decisions.md +27 -0
  203. package/pennyfarthing-dist/workflows/research/steps-market/step-05-competitive-analysis.md +26 -0
  204. package/pennyfarthing-dist/workflows/research/steps-market/step-06-research-completion.md +35 -0
  205. package/pennyfarthing-dist/workflows/research/steps-technical/step-01-init.md +22 -0
  206. package/pennyfarthing-dist/workflows/research/steps-technical/step-02-technical-overview.md +25 -0
  207. package/pennyfarthing-dist/workflows/research/steps-technical/step-03-integration-patterns.md +26 -0
  208. package/pennyfarthing-dist/workflows/research/steps-technical/step-04-architectural-patterns.md +26 -0
  209. package/pennyfarthing-dist/workflows/research/steps-technical/step-05-implementation-research.md +29 -1
  210. package/pennyfarthing-dist/workflows/research/steps-technical/step-06-research-synthesis.md +37 -1
  211. package/pennyfarthing-dist/workflows/sprint-planning/steps/step-01-parse-epic-files.md +15 -0
  212. package/pennyfarthing-dist/workflows/sprint-planning/steps/step-02-build-sprint-status.md +17 -0
  213. package/pennyfarthing-dist/workflows/sprint-planning/steps/step-03-status-detection.md +16 -0
  214. package/pennyfarthing-dist/workflows/sprint-planning/steps/step-04-generate-status-file.md +17 -0
  215. package/pennyfarthing-dist/workflows/sprint-planning/steps/step-05-validate-and-report.md +22 -0
  216. package/pennyfarthing-dist/workflows/ux-design/steps/step-01-init.md +6 -0
  217. package/pennyfarthing-dist/workflows/ux-design/steps/step-01b-continue.md +6 -0
  218. package/pennyfarthing-dist/workflows/ux-design/steps/step-02-discovery.md +6 -0
  219. package/pennyfarthing-dist/workflows/ux-design/steps/step-03-core-experience.md +6 -0
  220. package/pennyfarthing-dist/workflows/ux-design/steps/step-04-emotional-response.md +6 -0
  221. package/pennyfarthing-dist/workflows/ux-design/steps/step-05-inspiration.md +6 -0
  222. package/pennyfarthing-dist/workflows/ux-design/steps/step-06-design-system.md +6 -0
  223. package/pennyfarthing-dist/workflows/ux-design/steps/step-07-defining-experience.md +6 -0
  224. package/pennyfarthing-dist/workflows/ux-design/steps/step-08-visual-foundation.md +6 -0
  225. package/pennyfarthing-dist/workflows/ux-design/steps/step-09-design-directions.md +6 -0
  226. package/pennyfarthing-dist/workflows/ux-design/steps/step-10-user-journeys.md +6 -0
  227. package/pennyfarthing-dist/workflows/ux-design/steps/step-11-component-strategy.md +6 -0
  228. package/pennyfarthing-dist/workflows/ux-design/steps/step-12-ux-patterns.md +6 -0
  229. package/pennyfarthing-dist/workflows/ux-design/steps/step-13-responsive-accessibility.md +6 -0
  230. package/pennyfarthing-dist/workflows/ux-design/steps/step-14-complete.md +6 -0
  231. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  232. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  233. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  234. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  235. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  236. package/pennyfarthing_scripts/context.py +414 -0
  237. package/pennyfarthing_scripts/migration/__init__.py +39 -0
  238. package/pennyfarthing_scripts/migration/__main__.py +10 -0
  239. package/pennyfarthing_scripts/migration/cli.py +304 -0
  240. package/pennyfarthing_scripts/migration/session.py +384 -0
  241. package/pennyfarthing_scripts/migration/skill.py +188 -0
  242. package/pennyfarthing_scripts/migration/step.py +229 -0
  243. package/pennyfarthing_scripts/migration/validate.py +282 -0
  244. package/pennyfarthing_scripts/patch_mode.py +449 -0
  245. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  246. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  247. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  248. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  249. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  250. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  251. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  252. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  253. package/pennyfarthing_scripts/prime/cli.py +201 -0
  254. package/pennyfarthing_scripts/prime/models.py +9 -0
  255. package/pennyfarthing_scripts/prime/persona.py +41 -0
  256. package/pennyfarthing_scripts/prime/tiers.py +201 -0
  257. package/pennyfarthing_scripts/schema_validation_hook.py +306 -0
  258. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  259. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  260. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  261. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  262. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  263. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  264. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  265. package/pennyfarthing_scripts/sprint/archive_epic.py +399 -0
  266. package/pennyfarthing_scripts/sprint/cli.py +100 -0
  267. package/pennyfarthing_scripts/sprint/import_epic.py +431 -0
  268. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  269. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  270. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  271. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  272. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  273. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/tests/test_patch_mode.py +830 -0
  275. package/pennyfarthing_scripts/tests/test_tiers.py +1090 -0
  276. package/pennyfarthing_scripts/tests/test_token_counting.py +559 -0
  277. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  278. package/pennyfarthing-dist/scripts/sprint/import-epic-to-future.sh +0 -10
  279. package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +0 -270
  280. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  281. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  287. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  288. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  289. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  290. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  294. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  295. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  296. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  297. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  298. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  305. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  306. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  307. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  308. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  309. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  314. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  315. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  316. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  318. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  319. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  320. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  321. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  322. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  323. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  324. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  325. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  326. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  327. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  328. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  329. 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)