@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,559 @@
1
+ """Tests for component-level token counting.
2
+
3
+ Story: MSSCI-12800 - Component-level token tracking
4
+ Epic: MSSCI-12793 - Tiered Context Injection System
5
+
6
+ This story adds granular token counting per injected component, allowing users
7
+ to see exactly where context tokens are being spent.
8
+
9
+ Acceptance Criteria:
10
+ - AC1: Each component in the context injection has an approximate token count
11
+ - AC2: Token breakdown is passed from Python prime script to TypeScript/UI
12
+ - AC3: DebugPanel displays a collapsible list of components with their token counts
13
+ - AC4: Token counts are approximate but reasonably accurate (~10% tolerance)
14
+
15
+ This file tests the Python side (AC1, AC2, AC4).
16
+ """
17
+
18
+ import pytest
19
+ from pathlib import Path
20
+ from unittest.mock import patch
21
+ import yaml
22
+ import json
23
+
24
+
25
+ # =============================================================================
26
+ # AC1: Each component has an approximate token count
27
+ # =============================================================================
28
+
29
+
30
+ class TestComponentTokenCounting:
31
+ """Tests for per-component token counting (AC1)."""
32
+
33
+ def test_load_tier_components_returns_token_counts(self, tmp_path: Path) -> None:
34
+ """Test load_tier_components returns token counts for each component."""
35
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
36
+
37
+ self._setup_complete_project(tmp_path)
38
+
39
+ result = load_tier_components(
40
+ tier=ContextTier.FULL,
41
+ agent_name="dev",
42
+ project_root=tmp_path,
43
+ )
44
+
45
+ # Result should include token_counts dict
46
+ assert "token_counts" in result
47
+ assert isinstance(result["token_counts"], dict)
48
+
49
+ def test_token_counts_include_all_full_tier_components(self, tmp_path: Path) -> None:
50
+ """Test FULL tier returns token counts for all components."""
51
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
52
+
53
+ self._setup_complete_project(tmp_path)
54
+
55
+ result = load_tier_components(
56
+ tier=ContextTier.FULL,
57
+ agent_name="dev",
58
+ project_root=tmp_path,
59
+ )
60
+
61
+ token_counts = result.get("token_counts", {})
62
+
63
+ # FULL tier should have counts for all these components
64
+ expected_components = [
65
+ "workflow_state",
66
+ "agent_definition",
67
+ "persona",
68
+ "behavior_guide",
69
+ "sprint_context",
70
+ "session_header",
71
+ "sidecars",
72
+ ]
73
+
74
+ for component in expected_components:
75
+ assert component in token_counts, f"Missing token count for {component}"
76
+ assert isinstance(token_counts[component], int), f"Token count for {component} should be int"
77
+ assert token_counts[component] >= 0, f"Token count for {component} should be non-negative"
78
+
79
+ def test_token_counts_are_positive_for_loaded_components(self, tmp_path: Path) -> None:
80
+ """Test that loaded components have positive token counts."""
81
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
82
+
83
+ self._setup_complete_project(tmp_path)
84
+
85
+ result = load_tier_components(
86
+ tier=ContextTier.FULL,
87
+ agent_name="dev",
88
+ project_root=tmp_path,
89
+ )
90
+
91
+ token_counts = result.get("token_counts", {})
92
+
93
+ # Components with content should have positive counts
94
+ assert token_counts.get("agent_definition", 0) > 0
95
+ assert token_counts.get("behavior_guide", 0) > 0
96
+ assert token_counts.get("sidecars", 0) > 0
97
+
98
+ def test_token_counts_zero_for_missing_components(self, tmp_path: Path) -> None:
99
+ """Test that missing optional components have zero token counts."""
100
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
101
+
102
+ # Minimal setup - only agent definition
103
+ pf_dir = tmp_path / ".pennyfarthing"
104
+ pf_dir.mkdir()
105
+ agents_dir = pf_dir / "agents"
106
+ agents_dir.mkdir()
107
+ (agents_dir / "dev.md").write_text("# Dev Agent")
108
+
109
+ result = load_tier_components(
110
+ tier=ContextTier.FULL,
111
+ agent_name="dev",
112
+ project_root=tmp_path,
113
+ )
114
+
115
+ token_counts = result.get("token_counts", {})
116
+
117
+ # Missing components should have 0 count
118
+ assert token_counts.get("sidecars", 0) == 0
119
+ assert token_counts.get("behavior_guide", 0) == 0
120
+
121
+ def test_refresh_tier_only_counts_included_components(self, tmp_path: Path) -> None:
122
+ """Test REFRESH tier only includes counts for its components."""
123
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
124
+
125
+ self._setup_complete_project(tmp_path)
126
+
127
+ result = load_tier_components(
128
+ tier=ContextTier.REFRESH,
129
+ agent_name="dev",
130
+ project_root=tmp_path,
131
+ )
132
+
133
+ token_counts = result.get("token_counts", {})
134
+
135
+ # REFRESH should have counts for workflow_state, sprint_context, session_header
136
+ assert "workflow_state" in token_counts
137
+ assert "sprint_context" in token_counts
138
+ assert "session_header" in token_counts
139
+
140
+ # REFRESH should NOT have counts for excluded components
141
+ # (or they should be 0)
142
+ assert token_counts.get("agent_definition", 0) == 0
143
+ assert token_counts.get("behavior_guide", 0) == 0
144
+ assert token_counts.get("sidecars", 0) == 0
145
+
146
+ def test_handoff_tier_only_counts_included_components(self, tmp_path: Path) -> None:
147
+ """Test HANDOFF tier only includes counts for its components."""
148
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
149
+
150
+ self._setup_complete_project(tmp_path)
151
+
152
+ result = load_tier_components(
153
+ tier=ContextTier.HANDOFF,
154
+ agent_name="dev",
155
+ project_root=tmp_path,
156
+ )
157
+
158
+ token_counts = result.get("token_counts", {})
159
+
160
+ # HANDOFF should have counts for workflow_state, agent_definition, persona_compressed
161
+ assert "workflow_state" in token_counts
162
+ assert "agent_definition" in token_counts
163
+ assert "persona_compressed" in token_counts or "persona" in token_counts
164
+
165
+ def test_minimal_tier_only_counts_workflow_state(self, tmp_path: Path) -> None:
166
+ """Test MINIMAL tier only counts workflow state."""
167
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
168
+
169
+ self._setup_complete_project(tmp_path)
170
+
171
+ result = load_tier_components(
172
+ tier=ContextTier.MINIMAL,
173
+ agent_name="dev",
174
+ project_root=tmp_path,
175
+ )
176
+
177
+ token_counts = result.get("token_counts", {})
178
+
179
+ # MINIMAL should only have workflow_state
180
+ assert "workflow_state" in token_counts
181
+ assert token_counts.get("workflow_state", 0) > 0
182
+
183
+ # All other components should be 0
184
+ assert token_counts.get("agent_definition", 0) == 0
185
+ assert token_counts.get("persona", 0) == 0
186
+ assert token_counts.get("behavior_guide", 0) == 0
187
+
188
+ def test_total_tokens_is_sum_of_components(self, tmp_path: Path) -> None:
189
+ """Test that total_tokens equals sum of component counts."""
190
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier
191
+
192
+ self._setup_complete_project(tmp_path)
193
+
194
+ result = load_tier_components(
195
+ tier=ContextTier.FULL,
196
+ agent_name="dev",
197
+ project_root=tmp_path,
198
+ )
199
+
200
+ token_counts = result.get("token_counts", {})
201
+ total_tokens = result.get("total_tokens", 0)
202
+
203
+ # Total should equal sum of components
204
+ component_sum = sum(token_counts.values())
205
+ assert total_tokens == component_sum
206
+
207
+ def _setup_complete_project(self, tmp_path: Path) -> None:
208
+ """Set up a complete project for component testing."""
209
+ pf_dir = tmp_path / ".pennyfarthing"
210
+ pf_dir.mkdir()
211
+
212
+ # Agent definition
213
+ agents_dir = pf_dir / "agents"
214
+ agents_dir.mkdir()
215
+ (agents_dir / "dev.md").write_text("# Dev Agent\n\nDeveloper agent with implementation focus.")
216
+
217
+ # Behavior guide
218
+ guides_dir = pf_dir / "guides"
219
+ guides_dir.mkdir()
220
+ (guides_dir / "agent-behavior.md").write_text("# Agent Behavior Guide\n\nShared protocols for all agents.")
221
+
222
+ # Sidecars
223
+ sidecar_dir = pf_dir / "sidecars" / "dev"
224
+ sidecar_dir.mkdir(parents=True)
225
+ (sidecar_dir / "patterns.md").write_text("# Dev Patterns\n\nDevelopment patterns documentation.")
226
+ (sidecar_dir / "gotchas.md").write_text("# Dev Gotchas\n\nCommon pitfalls to avoid.")
227
+
228
+ # Theme
229
+ (pf_dir / "config.local.yaml").write_text(yaml.dump({"theme": "test-theme"}))
230
+ themes_dir = pf_dir / "personas" / "themes"
231
+ themes_dir.mkdir(parents=True)
232
+ (themes_dir / "test-theme.yaml").write_text(yaml.dump({
233
+ "theme": {"name": "Test Theme", "user_title": "Developer"},
234
+ "agents": {
235
+ "dev": {
236
+ "character": "Test Developer",
237
+ "style": "Practical and efficient",
238
+ "role": "Implementation specialist",
239
+ "quote": "Ship it!",
240
+ }
241
+ }
242
+ }))
243
+
244
+ # Sprint
245
+ sprint_dir = tmp_path / "sprint"
246
+ sprint_dir.mkdir()
247
+ (sprint_dir / "current-sprint.yaml").write_text(yaml.dump({
248
+ "sprint": {"number": 12, "goal": "Test sprint"},
249
+ "epics": []
250
+ }))
251
+
252
+ # Session
253
+ session_dir = tmp_path / ".session"
254
+ session_dir.mkdir()
255
+ (session_dir / "test-session.md").write_text("# Test Session\n\n- **Phase:** green")
256
+
257
+
258
+ # =============================================================================
259
+ # AC2: Token breakdown passed from Python to TypeScript/UI
260
+ # =============================================================================
261
+
262
+
263
+ class TestTokenBreakdownOutput:
264
+ """Tests for token breakdown in output (AC2)."""
265
+
266
+ def test_json_output_includes_token_counts(self, tmp_path: Path, capsys) -> None:
267
+ """Test JSON output includes token_counts object."""
268
+ from pennyfarthing_scripts.prime.cli import prime
269
+
270
+ self._setup_project(tmp_path)
271
+
272
+ with patch("pennyfarthing_scripts.prime.cli.get_project_root", return_value=tmp_path):
273
+ result = prime(
274
+ agent_name="dev",
275
+ tier="FULL",
276
+ json_output=True,
277
+ no_workflow=True,
278
+ no_register=True,
279
+ project_root=tmp_path,
280
+ )
281
+
282
+ assert result == 0
283
+ captured = capsys.readouterr()
284
+ data = json.loads(captured.out)
285
+
286
+ # JSON should include token_counts
287
+ assert "token_counts" in data
288
+ assert isinstance(data["token_counts"], dict)
289
+
290
+ def test_json_output_includes_total_tokens(self, tmp_path: Path, capsys) -> None:
291
+ """Test JSON output includes total_tokens field."""
292
+ from pennyfarthing_scripts.prime.cli import prime
293
+
294
+ self._setup_project(tmp_path)
295
+
296
+ with patch("pennyfarthing_scripts.prime.cli.get_project_root", return_value=tmp_path):
297
+ result = prime(
298
+ agent_name="dev",
299
+ tier="FULL",
300
+ json_output=True,
301
+ no_workflow=True,
302
+ no_register=True,
303
+ project_root=tmp_path,
304
+ )
305
+
306
+ assert result == 0
307
+ captured = capsys.readouterr()
308
+ data = json.loads(captured.out)
309
+
310
+ # JSON should include total_tokens
311
+ assert "total_tokens" in data
312
+ assert isinstance(data["total_tokens"], int)
313
+ assert data["total_tokens"] > 0
314
+
315
+ def test_json_token_counts_match_tier(self, tmp_path: Path, capsys) -> None:
316
+ """Test JSON token counts reflect the tier's components."""
317
+ from pennyfarthing_scripts.prime.cli import prime
318
+
319
+ self._setup_project(tmp_path)
320
+
321
+ with patch("pennyfarthing_scripts.prime.cli.get_project_root", return_value=tmp_path):
322
+ # MINIMAL tier
323
+ result = prime(
324
+ agent_name="dev",
325
+ tier="MINIMAL",
326
+ json_output=True,
327
+ no_workflow=True,
328
+ no_register=True,
329
+ project_root=tmp_path,
330
+ )
331
+
332
+ assert result == 0
333
+ captured = capsys.readouterr()
334
+ data = json.loads(captured.out)
335
+
336
+ token_counts = data.get("token_counts", {})
337
+
338
+ # MINIMAL should only have workflow_state with tokens
339
+ assert token_counts.get("workflow_state", 0) > 0
340
+ # Other components should be 0 or missing
341
+ assert token_counts.get("agent_definition", 0) == 0
342
+ assert token_counts.get("behavior_guide", 0) == 0
343
+
344
+ def test_json_output_token_counts_per_component(self, tmp_path: Path, capsys) -> None:
345
+ """Test JSON output has individual component counts."""
346
+ from pennyfarthing_scripts.prime.cli import prime
347
+
348
+ self._setup_project(tmp_path)
349
+
350
+ with patch("pennyfarthing_scripts.prime.cli.get_project_root", return_value=tmp_path):
351
+ with patch("pennyfarthing_scripts.prime.loader.get_project_root", return_value=tmp_path):
352
+ result = prime(
353
+ agent_name="dev",
354
+ tier="FULL",
355
+ json_output=True,
356
+ no_register=True,
357
+ project_root=tmp_path,
358
+ )
359
+
360
+ assert result == 0
361
+ captured = capsys.readouterr()
362
+ data = json.loads(captured.out)
363
+
364
+ token_counts = data.get("token_counts", {})
365
+
366
+ # Should have individual component entries
367
+ expected_keys = ["workflow_state", "agent_definition", "persona", "behavior_guide"]
368
+ for key in expected_keys:
369
+ assert key in token_counts, f"Missing {key} in token_counts"
370
+
371
+ def _setup_project(self, tmp_path: Path) -> None:
372
+ """Set up project for output testing."""
373
+ pf_dir = tmp_path / ".pennyfarthing"
374
+ pf_dir.mkdir()
375
+
376
+ agents_dir = pf_dir / "agents"
377
+ agents_dir.mkdir()
378
+ (agents_dir / "dev.md").write_text("# Dev Agent\n\nA developer agent.")
379
+
380
+ guides_dir = pf_dir / "guides"
381
+ guides_dir.mkdir()
382
+ (guides_dir / "agent-behavior.md").write_text("# Behavior Guide")
383
+
384
+ (pf_dir / "config.local.yaml").write_text(yaml.dump({"theme": "test"}))
385
+ themes_dir = pf_dir / "personas" / "themes"
386
+ themes_dir.mkdir(parents=True)
387
+ (themes_dir / "test.yaml").write_text(yaml.dump({
388
+ "agents": {"dev": {"character": "Dev", "style": "s", "role": "r"}}
389
+ }))
390
+
391
+
392
+ # =============================================================================
393
+ # AC4: Token counts are accurate within 10% tolerance
394
+ # =============================================================================
395
+
396
+
397
+ class TestTokenCountAccuracy:
398
+ """Tests for token count accuracy (AC4)."""
399
+
400
+ def test_token_count_uses_tiktoken_or_approximation(self) -> None:
401
+ """Test token counting uses tiktoken or reasonable approximation."""
402
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
403
+
404
+ # Test string with known token characteristics
405
+ # "Hello, world!" is typically 4 tokens in cl100k_base
406
+ text = "Hello, world!"
407
+ count = estimate_tokens(text)
408
+
409
+ # Should be close to 4 tokens (allow 10% tolerance = 1 token)
410
+ assert 3 <= count <= 5, f"Token count {count} not within expected range for 'Hello, world!'"
411
+
412
+ def test_token_count_scales_with_text_length(self) -> None:
413
+ """Test token count increases proportionally with text length."""
414
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
415
+
416
+ short_text = "Hello"
417
+ medium_text = "Hello " * 10
418
+ long_text = "Hello " * 100
419
+
420
+ short_count = estimate_tokens(short_text)
421
+ medium_count = estimate_tokens(medium_text)
422
+ long_count = estimate_tokens(long_text)
423
+
424
+ # Counts should increase with length
425
+ assert short_count < medium_count < long_count
426
+
427
+ def test_token_count_handles_empty_string(self) -> None:
428
+ """Test token counting handles empty string gracefully."""
429
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
430
+
431
+ count = estimate_tokens("")
432
+ assert count == 0
433
+
434
+ def test_token_count_handles_unicode(self) -> None:
435
+ """Test token counting handles unicode text."""
436
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
437
+
438
+ # Unicode text (emojis typically use multiple tokens)
439
+ text = "Hello 👋 World 🌍"
440
+ count = estimate_tokens(text)
441
+
442
+ assert count > 0
443
+ # Should handle without error
444
+
445
+ def test_token_count_handles_markdown(self) -> None:
446
+ """Test token counting handles markdown formatting."""
447
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
448
+
449
+ markdown = """# Heading
450
+
451
+ This is a **bold** statement with `code` and:
452
+ - List item 1
453
+ - List item 2
454
+
455
+ ```python
456
+ def hello():
457
+ print("world")
458
+ ```
459
+ """
460
+ count = estimate_tokens(markdown)
461
+
462
+ # Markdown should count all characters including formatting
463
+ assert count > 20 # Reasonable minimum for this content
464
+
465
+ def test_component_token_count_within_10_percent_of_actual(self, tmp_path: Path) -> None:
466
+ """Test component token counts are within 10% of actual tiktoken count."""
467
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier, estimate_tokens
468
+
469
+ # Create a known-content project
470
+ pf_dir = tmp_path / ".pennyfarthing"
471
+ pf_dir.mkdir()
472
+ agents_dir = pf_dir / "agents"
473
+ agents_dir.mkdir()
474
+
475
+ # Write a file with known content
476
+ agent_content = "# Developer Agent\n\n" + ("Test content. " * 50)
477
+ (agents_dir / "dev.md").write_text(agent_content)
478
+
479
+ # Get token count from load_tier_components
480
+ result = load_tier_components(
481
+ tier=ContextTier.FULL,
482
+ agent_name="dev",
483
+ project_root=tmp_path,
484
+ )
485
+
486
+ reported_count = result.get("token_counts", {}).get("agent_definition", 0)
487
+
488
+ # Get actual token count
489
+ actual_count = estimate_tokens(agent_content)
490
+
491
+ # Should be within 10%
492
+ tolerance = actual_count * 0.1
493
+ assert abs(reported_count - actual_count) <= tolerance, \
494
+ f"Reported {reported_count} vs actual {actual_count}, tolerance {tolerance}"
495
+
496
+ def test_total_tokens_within_10_percent_of_sum(self, tmp_path: Path) -> None:
497
+ """Test total_tokens is within 10% of manually summed content."""
498
+ from pennyfarthing_scripts.prime.tiers import load_tier_components, ContextTier, estimate_tokens
499
+
500
+ pf_dir = tmp_path / ".pennyfarthing"
501
+ pf_dir.mkdir()
502
+ agents_dir = pf_dir / "agents"
503
+ agents_dir.mkdir()
504
+ (agents_dir / "dev.md").write_text("# Dev Agent")
505
+
506
+ guides_dir = pf_dir / "guides"
507
+ guides_dir.mkdir()
508
+ (guides_dir / "agent-behavior.md").write_text("# Behavior Guide")
509
+
510
+ result = load_tier_components(
511
+ tier=ContextTier.FULL,
512
+ agent_name="dev",
513
+ project_root=tmp_path,
514
+ )
515
+
516
+ total_tokens = result.get("total_tokens", 0)
517
+ token_counts = result.get("token_counts", {})
518
+ component_sum = sum(token_counts.values())
519
+
520
+ # Total should match component sum
521
+ assert total_tokens == component_sum
522
+
523
+
524
+ # =============================================================================
525
+ # Utility function tests
526
+ # =============================================================================
527
+
528
+
529
+ class TestEstimateTokensFunction:
530
+ """Tests for the estimate_tokens utility function."""
531
+
532
+ def test_estimate_tokens_exists(self) -> None:
533
+ """Test estimate_tokens function is exported from tiers module."""
534
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
535
+ assert callable(estimate_tokens)
536
+
537
+ def test_estimate_tokens_returns_int(self) -> None:
538
+ """Test estimate_tokens returns an integer."""
539
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
540
+
541
+ result = estimate_tokens("Hello, world!")
542
+ assert isinstance(result, int)
543
+
544
+ def test_estimate_tokens_positive_for_content(self) -> None:
545
+ """Test estimate_tokens returns positive value for non-empty content."""
546
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
547
+
548
+ result = estimate_tokens("Some content here")
549
+ assert result > 0
550
+
551
+ def test_estimate_tokens_deterministic(self) -> None:
552
+ """Test estimate_tokens returns same result for same input."""
553
+ from pennyfarthing_scripts.prime.tiers import estimate_tokens
554
+
555
+ text = "Consistent input text"
556
+ result1 = estimate_tokens(text)
557
+ result2 = estimate_tokens(text)
558
+
559
+ assert result1 == result2
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env bash
2
- # import-epic-to-future.sh - Import epics-and-stories workflow output to future.yaml
3
- #
4
- # Usage: ./scripts/sprint/import-epic-to-future.sh [--dry-run] <epics-md-file> [initiative-name]
5
-
6
- set -euo pipefail
7
-
8
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
-
10
- exec python3 "$SCRIPT_DIR/import_epic_to_future.py" "$@"