@pennyfarthing/core 10.0.3 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +9 -7
  2. package/package.json +7 -1
  3. package/packages/core/dist/cli/commands/cyclist.d.ts +5 -1
  4. package/packages/core/dist/cli/commands/cyclist.d.ts.map +1 -1
  5. package/packages/core/dist/cli/commands/cyclist.js +4 -4
  6. package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
  7. package/packages/core/dist/cli/commands/cyclist.test.js +2 -2
  8. package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
  9. package/packages/core/dist/cli/commands/doctor-legacy.test.js +17 -16
  10. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  12. package/packages/core/dist/cli/commands/doctor.js +251 -4
  13. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  14. package/packages/core/dist/cli/commands/init.d.ts +7 -0
  15. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  16. package/packages/core/dist/cli/commands/init.js +43 -7
  17. package/packages/core/dist/cli/commands/init.js.map +1 -1
  18. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  19. package/packages/core/dist/cli/commands/update.js +26 -0
  20. package/packages/core/dist/cli/commands/update.js.map +1 -1
  21. package/packages/core/dist/cli/index.js +1 -1
  22. package/packages/core/dist/cli/index.js.map +1 -1
  23. package/packages/core/dist/cli/ocean-profiles.test.js +1 -1
  24. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  25. package/packages/core/dist/cli/utils/files.d.ts +10 -0
  26. package/packages/core/dist/cli/utils/files.d.ts.map +1 -1
  27. package/packages/core/dist/cli/utils/files.js +35 -0
  28. package/packages/core/dist/cli/utils/files.js.map +1 -1
  29. package/packages/core/dist/cli/utils/python.d.ts +22 -0
  30. package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
  31. package/packages/core/dist/cli/utils/python.js +102 -0
  32. package/packages/core/dist/cli/utils/python.js.map +1 -0
  33. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  34. package/packages/core/dist/cli/utils/settings.js +10 -0
  35. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  36. package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
  37. package/packages/core/dist/scripts/generate-report.js +11 -7
  38. package/packages/core/dist/scripts/generate-report.js.map +1 -1
  39. package/packages/core/dist/scripts/generate-spider-report.d.ts.map +1 -1
  40. package/packages/core/dist/scripts/generate-spider-report.js +12 -8
  41. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  42. package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
  43. package/packages/core/dist/scripts/generate-spider.js +6 -4
  44. package/packages/core/dist/scripts/generate-spider.js.map +1 -1
  45. package/packages/core/dist/scripts/generate-spider.test.js +2 -2
  46. package/packages/core/dist/scripts/generate-spider.test.js.map +1 -1
  47. package/pennyfarthing-dist/agents/README.md +1 -3
  48. package/pennyfarthing-dist/agents/architect.md +0 -6
  49. package/pennyfarthing-dist/agents/devops.md +0 -6
  50. package/pennyfarthing-dist/agents/orchestrator.md +0 -6
  51. package/pennyfarthing-dist/agents/pm.md +1 -7
  52. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  53. package/pennyfarthing-dist/agents/sm-setup.md +2 -2
  54. package/pennyfarthing-dist/agents/sm.md +4 -11
  55. package/pennyfarthing-dist/commands/architect.md +11 -3
  56. package/pennyfarthing-dist/commands/close-epic.md +24 -131
  57. package/pennyfarthing-dist/commands/create-theme.md +14 -24
  58. package/pennyfarthing-dist/commands/dev.md +11 -3
  59. package/pennyfarthing-dist/commands/devops.md +11 -3
  60. package/pennyfarthing-dist/commands/health-check.md +1 -3
  61. package/pennyfarthing-dist/commands/help.md +8 -12
  62. package/pennyfarthing-dist/commands/list-themes.md +14 -16
  63. package/pennyfarthing-dist/commands/orchestrator.md +11 -3
  64. package/pennyfarthing-dist/commands/parallel-work.md +1 -3
  65. package/pennyfarthing-dist/commands/pm.md +11 -3
  66. package/pennyfarthing-dist/commands/prime.md +6 -6
  67. package/pennyfarthing-dist/commands/repo-status.md +2 -2
  68. package/pennyfarthing-dist/commands/reviewer.md +11 -3
  69. package/pennyfarthing-dist/commands/run-ci.md +1 -1
  70. package/pennyfarthing-dist/commands/set-theme.md +14 -51
  71. package/pennyfarthing-dist/commands/setup.md +1 -1
  72. package/pennyfarthing-dist/commands/show-theme.md +14 -16
  73. package/pennyfarthing-dist/commands/sm.md +11 -3
  74. package/pennyfarthing-dist/commands/sprint.md +8 -8
  75. package/pennyfarthing-dist/commands/tea.md +11 -3
  76. package/pennyfarthing-dist/commands/tech-writer.md +11 -3
  77. package/pennyfarthing-dist/commands/theme-maker.md +14 -671
  78. package/pennyfarthing-dist/commands/theme.md +95 -0
  79. package/pennyfarthing-dist/commands/ux-designer.md +11 -3
  80. package/pennyfarthing-dist/commands/work.md +3 -5
  81. package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
  82. package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
  83. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
  84. package/pennyfarthing-dist/guides/hooks.md +5 -5
  85. package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
  86. package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
  87. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
  88. package/pennyfarthing-dist/guides/prime.md +2 -2
  89. package/pennyfarthing-dist/guides/skill-schema.md +25 -26
  90. package/pennyfarthing-dist/guides/xml-tags.md +2 -2
  91. package/pennyfarthing-dist/scripts/README.md +2 -2
  92. package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
  93. package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
  94. package/pennyfarthing-dist/scripts/git/git-status-all.sh +1 -1
  95. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
  96. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +3 -3
  97. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +14 -12
  98. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
  99. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
  100. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +1 -1
  101. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  102. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +3 -3
  103. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
  104. package/pennyfarthing-dist/scripts/sprint/README.md +32 -17
  105. package/pennyfarthing-dist/scripts/story/README.md +1 -1
  106. package/pennyfarthing-dist/scripts/test/test-setup.sh +1 -1
  107. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
  108. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
  109. package/pennyfarthing-dist/scripts/theme/README.md +1 -1
  110. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
  111. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +62 -17
  112. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
  113. package/pennyfarthing-dist/skills/skill-registry.yaml +41 -28
  114. package/pennyfarthing-dist/skills/sprint/skill.md +386 -68
  115. package/pennyfarthing-dist/skills/story/skill.md +14 -206
  116. package/pennyfarthing-dist/skills/theme/skill.md +290 -75
  117. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
  118. package/pennyfarthing-dist/skills/workflow/skill.md +4 -4
  119. package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
  120. package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
  121. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  122. package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
  123. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +42 -25
  124. package/pennyfarthing-dist/workflows/git-cleanup.yaml +1 -1
  125. package/pennyfarthing-dist/workflows/project-setup/steps/step-10-complete.md +1 -1
  126. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/cli.py +15 -0
  131. package/pennyfarthing_scripts/codemarkers/__init__.py +19 -0
  132. package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
  133. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  135. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  136. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  137. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  138. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/codemarkers/analyze.py +326 -0
  140. package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
  141. package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
  142. package/pennyfarthing_scripts/codemarkers/models.py +45 -0
  143. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/common/config.py +2 -1
  146. package/pennyfarthing_scripts/complexity/__init__.py +15 -0
  147. package/pennyfarthing_scripts/complexity/__main__.py +6 -0
  148. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  149. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  150. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  151. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  153. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  154. package/pennyfarthing_scripts/complexity/analyze.py +207 -0
  155. package/pennyfarthing_scripts/complexity/cli.py +78 -0
  156. package/pennyfarthing_scripts/complexity/formatters.py +64 -0
  157. package/pennyfarthing_scripts/complexity/models.py +32 -0
  158. package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
  159. package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
  160. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  162. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  163. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  164. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  165. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  166. package/pennyfarthing_scripts/deadcode/analyze.py +323 -0
  167. package/pennyfarthing_scripts/deadcode/cli.py +163 -0
  168. package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
  169. package/pennyfarthing_scripts/deadcode/models.py +54 -0
  170. package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
  171. package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
  172. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  173. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  174. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  175. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  176. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  177. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  178. package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
  179. package/pennyfarthing_scripts/dependencies/cli.py +72 -0
  180. package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
  181. package/pennyfarthing_scripts/dependencies/models.py +39 -0
  182. package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
  183. package/pennyfarthing_scripts/healthscore/__main__.py +6 -0
  184. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  185. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  186. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  187. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  188. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  189. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  190. package/pennyfarthing_scripts/healthscore/analyze.py +161 -0
  191. package/pennyfarthing_scripts/healthscore/cli.py +76 -0
  192. package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
  193. package/pennyfarthing_scripts/healthscore/models.py +44 -0
  194. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  195. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  196. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/hotspots/analyze.py +28 -1
  201. package/pennyfarthing_scripts/hotspots/cli.py +11 -9
  202. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  204. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  205. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  206. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  207. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  209. package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
  210. package/pennyfarthing_scripts/jira/cli.py +78 -1
  211. package/pennyfarthing_scripts/jira/client.py +28 -0
  212. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  213. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  214. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  215. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  216. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  217. package/pennyfarthing_scripts/prime/workflow.py +5 -3
  218. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  219. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  220. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  221. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  222. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  223. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  224. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  225. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  226. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  227. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  228. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  229. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  230. package/pennyfarthing_scripts/sprint/archive.py +63 -6
  231. package/pennyfarthing_scripts/sprint/archive_epic.py +198 -85
  232. package/pennyfarthing_scripts/sprint/cli.py +1565 -65
  233. package/pennyfarthing_scripts/sprint/epic_add.py +173 -0
  234. package/pennyfarthing_scripts/sprint/loader.py +46 -2
  235. package/pennyfarthing_scripts/sprint/story_add.py +202 -27
  236. package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
  237. package/pennyfarthing_scripts/sprint/validate_cmd.py +44 -5
  238. package/pennyfarthing_scripts/sprint/validator.py +13 -3
  239. package/pennyfarthing_scripts/sprint/work.py +43 -3
  240. package/pennyfarthing_scripts/sprint/yaml_io.py +124 -15
  241. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  242. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  243. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  244. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  245. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  246. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  247. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  248. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  249. package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
  250. package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
  251. package/pennyfarthing_scripts/tests/test_sprint_package.py +166 -0
  252. package/pennyfarthing_scripts/tests/test_yaml_io.py +117 -0
  253. package/pennyfarthing_scripts/theme/__init__.py +5 -0
  254. package/pennyfarthing_scripts/theme/__main__.py +6 -0
  255. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  256. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  257. package/pennyfarthing_scripts/theme/cli.py +286 -0
  258. package/scripts/README.md +53 -0
  259. package/scripts/postinstall.cjs +34 -0
  260. package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
  261. package/pennyfarthing-dist/scripts/sprint/archive-story.sh +0 -133
  262. package/pennyfarthing-dist/scripts/sprint/available-stories.sh +0 -91
  263. package/pennyfarthing-dist/scripts/sprint/check-story.sh +0 -158
  264. package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +0 -52
  265. package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +0 -63
  266. package/pennyfarthing-dist/scripts/sprint/list-future.sh +0 -145
  267. package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +0 -110
  268. package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +0 -148
  269. package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +0 -415
  270. package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +0 -33
  271. package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +0 -230
  272. package/pennyfarthing-dist/scripts/sprint/sprint-status.sh +0 -134
  273. package/pennyfarthing-dist/scripts/sprint/validate-sprint-yaml.sh +0 -139
  274. package/pennyfarthing-dist/skills/sprint/scripts/archive-story.sh +0 -101
  275. package/pennyfarthing-dist/skills/sprint/scripts/available-stories.sh +0 -97
  276. package/pennyfarthing-dist/skills/sprint/scripts/check-story.sh +0 -164
  277. package/pennyfarthing-dist/skills/sprint/scripts/create-jira-epic.sh +0 -23
  278. package/pennyfarthing-dist/skills/sprint/scripts/new-sprint.sh +0 -116
  279. package/pennyfarthing-dist/skills/sprint/scripts/promote-epic.sh +0 -164
  280. package/pennyfarthing-dist/skills/sprint/scripts/sprint-info.sh +0 -39
  281. package/pennyfarthing-dist/skills/sprint/scripts/sprint-status.sh +0 -147
  282. package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +0 -23
@@ -10,9 +10,6 @@ thisStepFile: './step-05-import-to-future.md'
10
10
  workflowFile: '{workflow_path}/workflow.yaml'
11
11
  outputFile: '{planning_artifacts}/epics.md'
12
12
  futureYaml: '{project_root}/sprint/future.yaml'
13
-
14
- # Script References
15
- importScript: '{project_root}/.pennyfarthing/scripts/sprint/import-epic-to-future.sh'
16
13
  ---
17
14
 
18
15
  <purpose>
@@ -21,12 +18,13 @@ To import the validated and complete epics and stories from the epics.md documen
21
18
 
22
19
  <instructions>
23
20
  1. Determine the initiative name from the epics document (prompt user if not obvious)
24
- 2. Run the import script in dry-run mode to show what will be added
25
- 3. Display the preview to the user showing epic numbers, initiative structure, and story IDs
26
- 4. Get user confirmation that the preview looks correct
27
- 5. If confirmed, run the import script without dry-run to apply changes to future.yaml
28
- 6. Verify the import by checking that epic appears in future.yaml with correct numbering
29
- 7. Display completion message with epic number, initiative name, and story count
21
+ 2. Read current future.yaml to find the next epic number (highest epic-N + 1)
22
+ 3. Read the validated epics.md output and construct the YAML structure
23
+ 4. Display a preview to the user showing epic numbers, initiative structure, and story IDs
24
+ 5. Get user confirmation that the preview looks correct
25
+ 6. If confirmed, append the new initiative and epics to future.yaml using yq
26
+ 7. Verify the import by checking that epic appears in future.yaml with correct numbering
27
+ 8. Display completion message with epic number, initiative name, and story count
30
28
  </instructions>
31
29
 
32
30
  <output>
@@ -72,34 +70,53 @@ Look at the epics document title and ask user:
72
70
 
73
71
  Wait for user confirmation or alternative name.
74
72
 
75
- ### 2. Preview Import (Dry Run)
73
+ ### 2. Determine Next Epic Number
76
74
 
77
- Run the import script with `--dry-run` to show what will be added:
75
+ Read `sprint/future.yaml` and find the highest `epic-N` ID currently in use:
78
76
 
79
77
  ```bash
80
- .pennyfarthing/scripts/sprint/import-epic-to-future.sh {outputFile} "{initiative_name}" --dry-run
78
+ yq '.future.initiatives[].epics[].id' sprint/future.yaml | grep -oE 'epic-[0-9]+' | sed 's/epic-//' | sort -n | tail -1
79
+ ```
80
+
81
+ The next epic gets `epic-{N+1}`. Stories use `{N+1}-{story_number}` format.
82
+
83
+ ### 3. Construct and Preview
84
+
85
+ Read the validated epics.md output file. For each epic, construct the YAML structure:
86
+
87
+ ```yaml
88
+ - id: epic-{N}
89
+ title: "Epic: {epic_title}"
90
+ points: {total_points}
91
+ priority: {priority}
92
+ repos: {repo}
93
+ stories:
94
+ - id: "{N}-1"
95
+ title: "{story_title}"
96
+ points: {points}
97
+ type: {type}
98
+ status: backlog
99
+ workflow: {workflow}
100
+ priority: {priority}
101
+ repos: {repo}
81
102
  ```
82
103
 
83
- Display the preview output to the user showing:
104
+ Display the full preview to the user showing:
84
105
  - Next epic number that will be assigned
85
106
  - Initiative structure
86
107
  - All stories with IDs
87
108
 
88
- ### 3. Confirm and Apply
109
+ ### 4. Confirm and Apply
89
110
 
90
111
  Ask user: "Does this look correct? [Y] Yes, import to future.yaml / [N] No, make changes"
91
112
 
92
113
  **If Y:**
93
- Run the import without `--dry-run`:
94
-
95
- ```bash
96
- .pennyfarthing/scripts/sprint/import-epic-to-future.sh {outputFile} "{initiative_name}"
97
- ```
114
+ Append the new initiative and epics to `sprint/future.yaml` using yq or direct YAML editing.
98
115
 
99
116
  **If N:**
100
- Ask what changes are needed and help user adjust before re-running.
117
+ Ask what changes are needed and help user adjust before re-applying.
101
118
 
102
- ### 4. Verify Import
119
+ ### 5. Verify Import
103
120
 
104
121
  After successful import, verify by showing:
105
122
 
@@ -112,7 +129,7 @@ Confirm:
112
129
  - Epic number is correct
113
130
  - Stories have proper IDs
114
131
 
115
- ### 5. Complete Workflow
132
+ ### 6. Complete Workflow
116
133
 
117
134
  Display completion message:
118
135
 
@@ -126,7 +143,7 @@ Display completion message:
126
143
 
127
144
  Next steps:
128
145
  - Use `/sprint` to view the backlog
129
- - Use `promote-epic.sh` to move to a sprint when ready
146
+ - Use `pf sprint epic promote` to move to a sprint when ready
130
147
  ```
131
148
 
132
149
  ## SUCCESS CRITERIA:
@@ -138,8 +155,8 @@ Next steps:
138
155
 
139
156
  ## FAILURE MODES:
140
157
 
141
- - ❌ Import script not found - check .pennyfarthing symlinks
142
158
  - ❌ future.yaml not found - ensure sprint/ directory exists
143
- - ❌ Duplicate epic number - script should handle this automatically
159
+ - ❌ Duplicate epic number - check existing IDs before assigning
160
+ - ❌ yq not installed - required for YAML manipulation (`brew install yq`)
144
161
 
145
162
  **Master Rule:** The workflow is not complete until epics are in future.yaml and accessible via sprint commands.
@@ -24,7 +24,7 @@ workflow:
24
24
  # Variables available in step files
25
25
  variables:
26
26
  output_file: .session/git-cleanup-plan.md
27
- repos_config: .claude/project/repos.yaml
27
+ repos_config: .pennyfarthing/repos.yaml
28
28
 
29
29
  # User approval gates
30
30
  gates:
@@ -146,7 +146,7 @@ IMMEDIATE:
146
146
 
147
147
  WHEN READY:
148
148
  □ Run /sm to start managing work
149
- □ Create stories with /story create
149
+ □ Create stories with /sprint story add
150
150
  □ Begin TDD workflow with /tea
151
151
 
152
152
  CUSTOMIZATION:
@@ -45,6 +45,21 @@ from pennyfarthing_scripts.jira.cli import jira
45
45
 
46
46
  cli.add_command(jira)
47
47
 
48
+ # Import and register deadcode group
49
+ from pennyfarthing_scripts.deadcode.cli import deadcode
50
+
51
+ cli.add_command(deadcode)
52
+
53
+ # Import and register theme group
54
+ from pennyfarthing_scripts.theme.cli import theme
55
+
56
+ cli.add_command(theme)
57
+
58
+ # Import and register healthscore group
59
+ from pennyfarthing_scripts.healthscore.cli import healthscore
60
+
61
+ cli.add_command(healthscore)
62
+
48
63
 
49
64
  @cli.group()
50
65
  def agent():
@@ -0,0 +1,19 @@
1
+ """
2
+ Code marker analysis — TODO, FIXME, HACK, XXX detection with git blame.
3
+
4
+ Story 80-1: Python codemarkers module.
5
+ """
6
+
7
+ from pennyfarthing_scripts.codemarkers.models import (
8
+ CodeMarker,
9
+ CodeMarkersResult,
10
+ MarkerSummary,
11
+ )
12
+ from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
13
+
14
+ __all__ = [
15
+ "CodeMarker",
16
+ "CodeMarkersResult",
17
+ "MarkerSummary",
18
+ "analyze_repo",
19
+ ]
@@ -0,0 +1,6 @@
1
+ """Allow running as: python -m pennyfarthing_scripts.codemarkers"""
2
+
3
+ from pennyfarthing_scripts.codemarkers.cli import codemarkers
4
+
5
+ if __name__ == "__main__":
6
+ codemarkers()
@@ -0,0 +1,326 @@
1
+ """
2
+ Core code marker analysis engine.
3
+
4
+ Greps source files for TODO/FIXME/HACK/XXX markers, runs git blame for
5
+ author and age data, and computes staleness.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import fnmatch
12
+ import re
13
+ import time
14
+ from collections import defaultdict
15
+ from datetime import datetime, timezone
16
+ from pathlib import Path
17
+
18
+ from pennyfarthing_scripts.codemarkers.models import (
19
+ CodeMarker,
20
+ CodeMarkersResult,
21
+ MarkerSummary,
22
+ )
23
+
24
+ # Marker types to detect (case-sensitive, uppercase only)
25
+ MARKER_PATTERN = re.compile(r"\b(TODO|FIXME|HACK|XXX)\b")
26
+
27
+ # Default file patterns to exclude from analysis
28
+ DEFAULT_EXCLUDES = [
29
+ "node_modules/*",
30
+ "dist/*",
31
+ "build/*",
32
+ "*.lock",
33
+ "*.min.js",
34
+ "*.min.css",
35
+ "*.map",
36
+ "package-lock.json",
37
+ "pnpm-lock.yaml",
38
+ ]
39
+
40
+ # Binary file extensions to skip
41
+ _BINARY_EXTENSIONS = frozenset({
42
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg",
43
+ ".woff", ".woff2", ".ttf", ".eot", ".otf",
44
+ ".zip", ".gz", ".tar", ".bz2", ".7z", ".rar",
45
+ ".pdf", ".doc", ".docx", ".xls", ".xlsx",
46
+ ".exe", ".dll", ".so", ".dylib", ".o", ".a",
47
+ ".pyc", ".pyo", ".class", ".jar",
48
+ ".mp3", ".mp4", ".wav", ".avi", ".mov",
49
+ ".sqlite", ".db",
50
+ })
51
+
52
+
53
+ def _should_exclude(path: str, patterns: list[str]) -> bool:
54
+ """Check if a file path matches any exclusion pattern."""
55
+ for pattern in patterns:
56
+ if fnmatch.fnmatch(path, pattern):
57
+ return True
58
+ # Also check the basename for patterns like "*.lock"
59
+ if fnmatch.fnmatch(path.split("/")[-1], pattern):
60
+ return True
61
+ return False
62
+
63
+
64
+ def _grep_markers(root: Path, excludes: list[str]) -> list[dict]:
65
+ """Scan files under root for TODO/FIXME/HACK/XXX markers.
66
+
67
+ Args:
68
+ root: Directory to scan recursively
69
+ excludes: Glob patterns for files/dirs to skip
70
+
71
+ Returns:
72
+ List of dicts: {path, line, marker_type, text}
73
+ """
74
+ results: list[dict] = []
75
+
76
+ for file_path in sorted(root.rglob("*")):
77
+ if not file_path.is_file():
78
+ continue
79
+
80
+ # Skip binary files by extension
81
+ if file_path.suffix.lower() in _BINARY_EXTENSIONS:
82
+ continue
83
+
84
+ # Get relative path for exclusion matching
85
+ rel_path = str(file_path.relative_to(root))
86
+
87
+ if _should_exclude(rel_path, excludes):
88
+ continue
89
+
90
+ try:
91
+ content = file_path.read_text(encoding="utf-8", errors="strict")
92
+ except (UnicodeDecodeError, OSError):
93
+ continue
94
+
95
+ for line_num, line_text in enumerate(content.split("\n"), start=1):
96
+ match = MARKER_PATTERN.search(line_text)
97
+ if match:
98
+ results.append({
99
+ "path": rel_path,
100
+ "line": line_num,
101
+ "marker_type": match.group(1),
102
+ "text": line_text.strip(),
103
+ })
104
+
105
+ return results
106
+
107
+
108
+ def _parse_blame_porcelain(output: str, line: int) -> dict:
109
+ """Parse git blame --porcelain output for a specific line.
110
+
111
+ Extracts author name and author-time (Unix timestamp).
112
+
113
+ Args:
114
+ output: Raw porcelain output from git blame
115
+ line: Line number to extract (used for context)
116
+
117
+ Returns:
118
+ Dict with 'author' and 'author_time' keys, or empty dict
119
+ """
120
+ if not output.strip():
121
+ return {}
122
+
123
+ author = ""
124
+ author_time = 0
125
+
126
+ for raw_line in output.split("\n"):
127
+ if raw_line.startswith("author "):
128
+ author = raw_line[7:]
129
+ elif raw_line.startswith("author-time "):
130
+ try:
131
+ author_time = int(raw_line[12:])
132
+ except ValueError:
133
+ pass
134
+
135
+ if not author:
136
+ return {}
137
+
138
+ return {"author": author, "author_time": author_time}
139
+
140
+
141
+ async def _run_git_command(args: list[str], cwd: Path) -> tuple[str, str, int]:
142
+ """Run a git command asynchronously.
143
+
144
+ Args:
145
+ args: Git command arguments (without 'git')
146
+ cwd: Working directory
147
+
148
+ Returns:
149
+ (stdout, stderr, return_code)
150
+ """
151
+ proc = await asyncio.create_subprocess_exec(
152
+ "git",
153
+ *args,
154
+ cwd=cwd,
155
+ stdout=asyncio.subprocess.PIPE,
156
+ stderr=asyncio.subprocess.PIPE,
157
+ )
158
+ stdout, stderr = await proc.communicate()
159
+ return (
160
+ stdout.decode("utf-8", errors="replace").strip(),
161
+ stderr.decode("utf-8", errors="replace").strip(),
162
+ proc.returncode or 0,
163
+ )
164
+
165
+
166
+ async def _batch_blame_file(
167
+ repo_path: Path, file_path: str, lines: list[int]
168
+ ) -> dict[int, dict]:
169
+ """Blame an entire file once and extract data for requested lines.
170
+
171
+ Args:
172
+ repo_path: Root of the git repository
173
+ file_path: Relative path to file within repo
174
+ lines: Line numbers to extract blame data for
175
+
176
+ Returns:
177
+ Dict mapping line number -> {author, author_time}
178
+ """
179
+ stdout, stderr, rc = await _run_git_command(
180
+ ["blame", "--porcelain", file_path], repo_path
181
+ )
182
+
183
+ if rc != 0:
184
+ return {}
185
+
186
+ # Parse porcelain: each block starts with "<hash> <orig_line> <final_line> <count>"
187
+ results: dict[int, dict] = {}
188
+ current_line = 0
189
+ current_author = ""
190
+ current_time = 0
191
+ lines_set = set(lines)
192
+
193
+ for raw_line in stdout.split("\n"):
194
+ # Header line: hash orig_line final_line [count]
195
+ # Detect by checking that parts[1] is a digit (orig_line number)
196
+ parts = raw_line.split(" ")
197
+ if len(parts) >= 3 and len(parts[0]) >= 6 and parts[1].isdigit():
198
+ try:
199
+ current_line = int(parts[2])
200
+ except (ValueError, IndexError):
201
+ pass
202
+ current_author = ""
203
+ current_time = 0
204
+ elif raw_line.startswith("author "):
205
+ current_author = raw_line[7:]
206
+ elif raw_line.startswith("author-time "):
207
+ try:
208
+ current_time = int(raw_line[12:])
209
+ except ValueError:
210
+ pass
211
+ elif raw_line.startswith("\t"):
212
+ # Content line — end of this block
213
+ if current_line in lines_set:
214
+ results[current_line] = {
215
+ "author": current_author,
216
+ "author_time": current_time,
217
+ }
218
+
219
+ return results
220
+
221
+
222
+ async def analyze_repo(
223
+ name: str,
224
+ path: Path,
225
+ days: int = 90,
226
+ excludes: list[str] | None = None,
227
+ ) -> CodeMarkersResult:
228
+ """Analyze a repository for code markers.
229
+
230
+ Args:
231
+ name: Display name for the repository
232
+ path: Path to the git repository
233
+ days: Stale threshold in days
234
+ excludes: Additional file patterns to exclude
235
+
236
+ Returns:
237
+ CodeMarkersResult with markers and summary
238
+ """
239
+ resolved = Path(path).resolve()
240
+
241
+ if not resolved.exists():
242
+ return CodeMarkersResult(
243
+ success=False,
244
+ repo_name=name,
245
+ repo_path=str(resolved),
246
+ stale_threshold_days=days,
247
+ error=f"Path not found: {resolved}",
248
+ )
249
+
250
+ all_excludes = DEFAULT_EXCLUDES + (excludes or [])
251
+
252
+ # Grep for markers
253
+ raw_markers = _grep_markers(resolved, all_excludes)
254
+
255
+ if not raw_markers:
256
+ return CodeMarkersResult(
257
+ success=True,
258
+ repo_name=name,
259
+ repo_path=str(resolved),
260
+ stale_threshold_days=days,
261
+ markers=[],
262
+ summary=MarkerSummary(),
263
+ )
264
+
265
+ # Group markers by file for batch blame
266
+ by_file: dict[str, list[dict]] = defaultdict(list)
267
+ for m in raw_markers:
268
+ by_file[m["path"]].append(m)
269
+
270
+ # Blame each file once
271
+ now = time.time()
272
+ markers: list[CodeMarker] = []
273
+ stale_count = 0
274
+ type_counts: dict[str, int] = defaultdict(int)
275
+
276
+ for file_path, file_markers in by_file.items():
277
+ line_numbers = [m["line"] for m in file_markers]
278
+ blame_data = await _batch_blame_file(resolved, file_path, line_numbers)
279
+
280
+ for m in file_markers:
281
+ blame = blame_data.get(m["line"], {})
282
+ author = blame.get("author", "")
283
+ author_time = blame.get("author_time", 0)
284
+
285
+ # Compute age
286
+ if author_time > 0:
287
+ age_days = (now - author_time) / 86400
288
+ date_str = datetime.fromtimestamp(
289
+ author_time, tz=timezone.utc
290
+ ).isoformat()
291
+ else:
292
+ age_days = 0.0
293
+ date_str = ""
294
+
295
+ is_stale = age_days > days
296
+
297
+ marker = CodeMarker(
298
+ path=m["path"],
299
+ line=m["line"],
300
+ marker_type=m["marker_type"],
301
+ text=m["text"],
302
+ author=author,
303
+ date=date_str,
304
+ age_days=round(age_days, 1),
305
+ is_stale=is_stale,
306
+ )
307
+ markers.append(marker)
308
+
309
+ type_counts[m["marker_type"]] += 1
310
+ if is_stale:
311
+ stale_count += 1
312
+
313
+ summary = MarkerSummary(
314
+ total_markers=len(markers),
315
+ stale_markers=stale_count,
316
+ by_type=dict(type_counts),
317
+ )
318
+
319
+ return CodeMarkersResult(
320
+ success=True,
321
+ repo_name=name,
322
+ repo_path=str(resolved),
323
+ stale_threshold_days=days,
324
+ markers=markers,
325
+ summary=summary,
326
+ )
@@ -0,0 +1,129 @@
1
+ """
2
+ CLI commands for code marker analysis.
3
+
4
+ Usage:
5
+ pf codemarkers analyze [OPTIONS]
6
+ pf codemarkers stale [OPTIONS]
7
+ pf codemarkers summary [OPTIONS]
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ from pathlib import Path
14
+
15
+ import click
16
+
17
+
18
+ @click.group()
19
+ def codemarkers():
20
+ """Code marker detection (TODO, FIXME, HACK, XXX).
21
+
22
+ \b
23
+ Commands:
24
+ analyze - Full marker analysis with blame data
25
+ stale - Show only stale markers (older than threshold)
26
+ summary - Summary counts by marker type
27
+ """
28
+ pass
29
+
30
+
31
+ def _common_options(fn):
32
+ """Shared options for all codemarkers commands."""
33
+ fn = click.option("--repo", help="Analyze a single named repo from repos.yaml")(fn)
34
+ fn = click.option("--path", "repo_path", type=click.Path(), help="Analyze a standalone repo path")(fn)
35
+ fn = click.option("--days", default=90, show_default=True, help="Stale threshold in days")(fn)
36
+ fn = click.option("--top", default=20, show_default=True, help="Number of top results to show")(fn)
37
+ fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]), default="table", show_default=True)(fn)
38
+ fn = click.option("--output", "output_file", type=click.Path(), help="Write output to file")(fn)
39
+ fn = click.option("--exclude", multiple=True, help="Additional exclude patterns (repeatable)")(fn)
40
+ return fn
41
+
42
+
43
+ def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple):
44
+ """Run analysis and return result."""
45
+ from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
46
+ from pennyfarthing_scripts.common.config import get_project_root
47
+
48
+ excludes = list(exclude) if exclude else None
49
+
50
+ if repo_path:
51
+ p = Path(repo_path).resolve()
52
+ return asyncio.run(analyze_repo(p.name, p, days, excludes))
53
+ elif repo:
54
+ project_root = get_project_root()
55
+ from pennyfarthing_scripts.common.config import load_yaml_config
56
+ repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
57
+ if repos_yaml and repo in repos_yaml:
58
+ cfg = repos_yaml[repo]
59
+ rpath = cfg.get("path", repo) if isinstance(cfg, dict) else str(cfg)
60
+ return asyncio.run(
61
+ analyze_repo(repo, project_root / rpath, days, excludes)
62
+ )
63
+ else:
64
+ candidate = project_root / repo
65
+ if candidate.exists():
66
+ return asyncio.run(analyze_repo(repo, candidate, days, excludes))
67
+ raise click.ClickException(f"Repo not found: {repo}")
68
+ else:
69
+ project_root = get_project_root()
70
+ return asyncio.run(
71
+ analyze_repo(project_root.name, project_root, days, excludes)
72
+ )
73
+
74
+
75
+ def _output_result(result, fmt: str, output_file: str | None, top: int, mode: str):
76
+ """Format and output the analysis result."""
77
+ from pennyfarthing_scripts.codemarkers.formatters import (
78
+ export_csv,
79
+ export_json,
80
+ format_marker_table,
81
+ format_summary,
82
+ )
83
+
84
+ if fmt == "json":
85
+ text = export_json(result)
86
+ elif fmt == "csv":
87
+ text = export_csv(result.markers[:top])
88
+ else:
89
+ format_summary(result)
90
+ if mode == "stale":
91
+ stale = [m for m in result.markers if m.is_stale]
92
+ text = format_marker_table(stale, top)
93
+ else:
94
+ text = format_marker_table(result.markers, top)
95
+
96
+ if output_file:
97
+ Path(output_file).write_text(text)
98
+ click.echo(f"Output written to {output_file}", err=True)
99
+ else:
100
+ click.echo(text)
101
+
102
+
103
+ @codemarkers.command()
104
+ @_common_options
105
+ def analyze(repo, repo_path, days, top, fmt, output_file, exclude):
106
+ """Full marker analysis with blame data."""
107
+ result = _run_analysis(repo, repo_path, days, exclude)
108
+ _output_result(result, fmt, output_file, top, "analyze")
109
+
110
+
111
+ @codemarkers.command()
112
+ @_common_options
113
+ def stale(repo, repo_path, days, top, fmt, output_file, exclude):
114
+ """Show only stale markers (older than threshold)."""
115
+ result = _run_analysis(repo, repo_path, days, exclude)
116
+ _output_result(result, fmt, output_file, top, "stale")
117
+
118
+
119
+ @codemarkers.command()
120
+ @_common_options
121
+ def summary(repo, repo_path, days, top, fmt, output_file, exclude):
122
+ """Summary counts by marker type."""
123
+ result = _run_analysis(repo, repo_path, days, exclude)
124
+ if fmt == "json":
125
+ from pennyfarthing_scripts.codemarkers.formatters import export_json
126
+ click.echo(export_json(result))
127
+ else:
128
+ from pennyfarthing_scripts.codemarkers.formatters import format_summary
129
+ format_summary(result, file=click.get_text_stream("stdout"))