@pennyfarthing/core 10.0.5 → 10.2.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 (580) hide show
  1. package/README.md +19 -22
  2. package/package.json +17 -11
  3. package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
  4. package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
  5. package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
  6. package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
  7. package/packages/core/dist/cli/commands/doctor.js +346 -13
  8. package/packages/core/dist/cli/commands/doctor.js.map +1 -1
  9. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
  10. package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
  11. package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
  12. package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
  13. package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
  14. package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
  15. package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
  16. package/packages/core/dist/cli/commands/init.d.ts +7 -0
  17. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  18. package/packages/core/dist/cli/commands/init.js +41 -8
  19. package/packages/core/dist/cli/commands/init.js.map +1 -1
  20. package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
  21. package/packages/core/dist/cli/commands/uninstall.js +24 -13
  22. package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
  23. package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
  24. package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
  25. package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
  26. package/packages/core/dist/cli/commands/update.js +26 -0
  27. package/packages/core/dist/cli/commands/update.js.map +1 -1
  28. package/packages/core/dist/cli/index.js +1 -1
  29. package/packages/core/dist/cli/index.js.map +1 -1
  30. package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
  31. package/packages/core/dist/cli/theme-maker.test.js +64 -115
  32. package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
  33. package/packages/core/dist/cli/utils/python.d.ts +22 -0
  34. package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
  35. package/packages/core/dist/cli/utils/python.js +102 -0
  36. package/packages/core/dist/cli/utils/python.js.map +1 -0
  37. package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
  38. package/packages/core/dist/cli/utils/settings.js +10 -0
  39. package/packages/core/dist/cli/utils/settings.js.map +1 -1
  40. package/packages/core/dist/index.d.ts +1 -1
  41. package/packages/core/dist/index.d.ts.map +1 -1
  42. package/packages/core/dist/index.js +2 -2
  43. package/packages/core/dist/index.js.map +1 -1
  44. package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
  45. package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
  46. package/packages/core/dist/plugins/plugin-discovery.js +165 -0
  47. package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
  48. package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
  49. package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
  50. package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
  51. package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
  52. package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
  53. package/packages/core/dist/workflow/context-watch.d.ts +80 -0
  54. package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
  55. package/packages/core/dist/workflow/context-watch.js +235 -0
  56. package/packages/core/dist/workflow/context-watch.js.map +1 -0
  57. package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
  58. package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
  59. package/packages/core/dist/workflow/context-watch.test.js +746 -0
  60. package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
  61. package/packages/core/dist/workflow/file-watch.d.ts +82 -0
  62. package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
  63. package/packages/core/dist/workflow/file-watch.js +198 -0
  64. package/packages/core/dist/workflow/file-watch.js.map +1 -0
  65. package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
  66. package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
  67. package/packages/core/dist/workflow/file-watch.test.js +469 -0
  68. package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
  69. package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
  70. package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
  71. package/packages/core/dist/workflow/observation-writer.js +97 -0
  72. package/packages/core/dist/workflow/observation-writer.js.map +1 -0
  73. package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
  74. package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
  75. package/packages/core/dist/workflow/observation-writer.test.js +424 -0
  76. package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
  77. package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
  78. package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
  79. package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
  80. package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
  81. package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
  82. package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
  83. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
  84. package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
  85. package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
  86. package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
  87. package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
  88. package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
  89. package/packages/core/dist/workflow/tool-watch.js +166 -0
  90. package/packages/core/dist/workflow/tool-watch.js.map +1 -0
  91. package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
  92. package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
  93. package/packages/core/dist/workflow/tool-watch.test.js +718 -0
  94. package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
  95. package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
  96. package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
  97. package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
  98. package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
  99. package/packages/core/dist/workflow/workflow-schema.js +44 -0
  100. package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
  101. package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
  102. package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
  103. package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
  104. package/pennyfarthing-dist/agents/README.md +1 -3
  105. package/pennyfarthing-dist/agents/architect.md +0 -6
  106. package/pennyfarthing-dist/agents/devops.md +0 -6
  107. package/pennyfarthing-dist/agents/handoff.md +18 -3
  108. package/pennyfarthing-dist/agents/orchestrator.md +0 -6
  109. package/pennyfarthing-dist/agents/pm.md +0 -6
  110. package/pennyfarthing-dist/agents/sm-finish.md +1 -1
  111. package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
  112. package/pennyfarthing-dist/agents/sm.md +11 -11
  113. package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
  114. package/pennyfarthing-dist/commands/architect.md +11 -3
  115. package/pennyfarthing-dist/commands/close-epic.md +24 -131
  116. package/pennyfarthing-dist/commands/create-theme.md +14 -24
  117. package/pennyfarthing-dist/commands/dev.md +11 -3
  118. package/pennyfarthing-dist/commands/devops.md +11 -3
  119. package/pennyfarthing-dist/commands/health-check.md +1 -3
  120. package/pennyfarthing-dist/commands/help.md +8 -12
  121. package/pennyfarthing-dist/commands/list-themes.md +14 -16
  122. package/pennyfarthing-dist/commands/orchestrator.md +11 -3
  123. package/pennyfarthing-dist/commands/parallel-work.md +1 -3
  124. package/pennyfarthing-dist/commands/pm.md +11 -3
  125. package/pennyfarthing-dist/commands/prime.md +6 -6
  126. package/pennyfarthing-dist/commands/reviewer.md +11 -3
  127. package/pennyfarthing-dist/commands/run-ci.md +1 -1
  128. package/pennyfarthing-dist/commands/set-theme.md +14 -51
  129. package/pennyfarthing-dist/commands/setup.md +5 -1
  130. package/pennyfarthing-dist/commands/show-theme.md +14 -16
  131. package/pennyfarthing-dist/commands/sm.md +11 -3
  132. package/pennyfarthing-dist/commands/tea.md +11 -3
  133. package/pennyfarthing-dist/commands/tech-writer.md +11 -3
  134. package/pennyfarthing-dist/commands/theme-maker.md +14 -671
  135. package/pennyfarthing-dist/commands/theme.md +95 -0
  136. package/pennyfarthing-dist/commands/ux-designer.md +11 -3
  137. package/pennyfarthing-dist/commands/work.md +3 -5
  138. package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
  139. package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
  140. package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
  141. package/pennyfarthing-dist/guides/bikelane.md +3 -2
  142. package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
  143. package/pennyfarthing-dist/guides/hooks.md +5 -5
  144. package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
  145. package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
  146. package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
  147. package/pennyfarthing-dist/guides/prime.md +2 -2
  148. package/pennyfarthing-dist/guides/scale-levels.md +4 -6
  149. package/pennyfarthing-dist/guides/skill-schema.md +4 -4
  150. package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
  151. package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
  152. package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
  153. package/pennyfarthing-dist/scripts/README.md +1 -1
  154. package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
  155. package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
  156. package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
  157. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  158. package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
  159. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  160. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
  161. package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
  162. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
  163. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  164. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
  165. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  166. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
  167. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  168. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  169. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  170. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
  171. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +32 -15
  172. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
  173. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  174. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
  175. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  176. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  177. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  178. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  179. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  180. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  181. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
  182. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  183. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  184. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  185. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  186. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  187. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  188. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  189. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  190. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  191. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  192. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  193. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  194. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  195. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  196. package/pennyfarthing-dist/scripts/misc/README.md +1 -1
  197. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  198. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  199. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  200. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  201. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  202. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  203. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  204. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  205. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
  206. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  207. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  208. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  209. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  210. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  211. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  212. package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
  213. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  214. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
  215. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  216. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  217. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  218. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  219. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  220. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  221. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  222. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
  223. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  224. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  225. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  226. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  227. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  228. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
  229. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  230. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  231. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  232. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  233. package/pennyfarthing-dist/scripts/theme/README.md +1 -1
  234. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  235. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
  236. package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
  237. package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
  238. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  239. package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
  240. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -144
  241. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
  242. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  243. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
  244. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
  245. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
  246. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
  247. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
  248. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
  249. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
  250. package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
  251. package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
  252. package/pennyfarthing-dist/skills/skill-registry.yaml +21 -17
  253. package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
  254. package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
  255. package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
  256. package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
  257. package/pennyfarthing-dist/skills/theme/skill.md +290 -75
  258. package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
  259. package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
  260. package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
  261. package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
  262. package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
  263. package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
  264. package/pennyfarthing-dist/skills/workflow/skill.md +27 -4
  265. package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
  266. package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
  267. package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
  268. package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
  269. package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
  270. package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
  271. package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +41 -24
  272. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
  273. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  274. package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
  275. package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
  276. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  277. package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
  278. package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
  279. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  280. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  281. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  282. package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
  283. package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
  284. package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
  285. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  286. package/pennyfarthing_scripts/bellmode_hook.py +202 -47
  287. package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
  288. package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
  289. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  290. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  291. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  292. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  293. package/pennyfarthing_scripts/brownfield/cli.py +0 -1
  294. package/pennyfarthing_scripts/brownfield/discover.py +1 -2
  295. package/pennyfarthing_scripts/cli.py +23 -3
  296. package/pennyfarthing_scripts/codemarkers/__init__.py +23 -0
  297. package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
  298. package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  299. package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
  300. package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  301. package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
  302. package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
  303. package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  304. package/pennyfarthing_scripts/codemarkers/analyze.py +501 -0
  305. package/pennyfarthing_scripts/codemarkers/cli.py +179 -0
  306. package/pennyfarthing_scripts/codemarkers/formatters.py +88 -0
  307. package/pennyfarthing_scripts/codemarkers/models.py +60 -0
  308. package/pennyfarthing_scripts/common/__init__.py +8 -9
  309. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  310. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  311. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  312. package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
  313. package/pennyfarthing_scripts/common/config.py +1 -1
  314. package/pennyfarthing_scripts/complexity/__init__.py +15 -0
  315. package/pennyfarthing_scripts/complexity/__main__.py +6 -0
  316. package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  317. package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
  318. package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  319. package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
  320. package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
  321. package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
  322. package/pennyfarthing_scripts/complexity/analyze.py +207 -0
  323. package/pennyfarthing_scripts/complexity/cli.py +82 -0
  324. package/pennyfarthing_scripts/complexity/formatters.py +64 -0
  325. package/pennyfarthing_scripts/complexity/models.py +32 -0
  326. package/pennyfarthing_scripts/context.py +14 -15
  327. package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
  328. package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
  329. package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  330. package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
  331. package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  332. package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  333. package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
  334. package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  335. package/pennyfarthing_scripts/deadcode/analyze.py +322 -0
  336. package/pennyfarthing_scripts/deadcode/cli.py +163 -0
  337. package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
  338. package/pennyfarthing_scripts/deadcode/models.py +54 -0
  339. package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
  340. package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
  341. package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  342. package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
  343. package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  344. package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
  345. package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
  346. package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  347. package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
  348. package/pennyfarthing_scripts/dependencies/cli.py +76 -0
  349. package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
  350. package/pennyfarthing_scripts/dependencies/models.py +39 -0
  351. package/pennyfarthing_scripts/git/__init__.py +5 -5
  352. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  353. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  354. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  355. package/pennyfarthing_scripts/git/create_branches.py +3 -2
  356. package/pennyfarthing_scripts/git/status_all.py +1 -1
  357. package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
  358. package/pennyfarthing_scripts/healthscore/__main__.py +14 -0
  359. package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  360. package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  361. package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  362. package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  363. package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  364. package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  365. package/pennyfarthing_scripts/healthscore/analyze.py +591 -0
  366. package/pennyfarthing_scripts/healthscore/cli.py +80 -0
  367. package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
  368. package/pennyfarthing_scripts/healthscore/models.py +43 -0
  369. package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  370. package/pennyfarthing_scripts/hooks.py +8 -11
  371. package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
  372. package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  373. package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
  374. package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  375. package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  376. package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
  377. package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  378. package/pennyfarthing_scripts/hotspots/analyze.py +155 -14
  379. package/pennyfarthing_scripts/hotspots/cli.py +12 -10
  380. package/pennyfarthing_scripts/hotspots/models.py +0 -1
  381. package/pennyfarthing_scripts/jira/__init__.py +15 -17
  382. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  383. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  384. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  385. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  386. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  387. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  388. package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
  389. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  390. package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
  391. package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  392. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  393. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  394. package/pennyfarthing_scripts/jira/bidirectional.py +44 -18
  395. package/pennyfarthing_scripts/jira/claim.py +21 -0
  396. package/pennyfarthing_scripts/jira/cli.py +6 -3
  397. package/pennyfarthing_scripts/jira/client.py +32 -4
  398. package/pennyfarthing_scripts/jira/create.py +45 -1
  399. package/pennyfarthing_scripts/jira/epic.py +3 -2
  400. package/pennyfarthing_scripts/jira/reconcile.py +0 -1
  401. package/pennyfarthing_scripts/jira/story.py +2 -0
  402. package/pennyfarthing_scripts/jira/sync.py +1 -1
  403. package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
  404. package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
  405. package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
  406. package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
  407. package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
  408. package/pennyfarthing_scripts/migration/skill.py +0 -1
  409. package/pennyfarthing_scripts/migration/step.py +0 -1
  410. package/pennyfarthing_scripts/migration/validate.py +8 -5
  411. package/pennyfarthing_scripts/patch_mode.py +2 -2
  412. package/pennyfarthing_scripts/preflight/__init__.py +1 -1
  413. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  414. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  415. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  416. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  417. package/pennyfarthing_scripts/preflight/finish.py +0 -1
  418. package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
  419. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  420. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  421. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  422. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  423. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  424. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  425. package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  426. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  427. package/pennyfarthing_scripts/prime/cli.py +5 -1
  428. package/pennyfarthing_scripts/prime/loader.py +2 -3
  429. package/pennyfarthing_scripts/prime/persona.py +2 -1
  430. package/pennyfarthing_scripts/prime/tiers.py +4 -4
  431. package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
  432. package/pennyfarthing_scripts/sprint/__init__.py +10 -12
  433. package/pennyfarthing_scripts/sprint/__main__.py +2 -2
  434. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  435. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  436. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  437. package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  438. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  439. package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  440. package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
  441. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  442. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  443. package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  444. package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  445. package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  446. package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  447. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  448. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  449. package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  450. package/pennyfarthing_scripts/sprint/archive.py +0 -1
  451. package/pennyfarthing_scripts/sprint/archive_epic.py +198 -97
  452. package/pennyfarthing_scripts/sprint/cli.py +62 -46
  453. package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
  454. package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
  455. package/pennyfarthing_scripts/sprint/loader.py +6 -0
  456. package/pennyfarthing_scripts/sprint/status.py +1 -2
  457. package/pennyfarthing_scripts/sprint/story_add.py +202 -27
  458. package/pennyfarthing_scripts/sprint/story_finish.py +209 -0
  459. package/pennyfarthing_scripts/sprint/story_update.py +11 -3
  460. package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
  461. package/pennyfarthing_scripts/sprint/validator.py +120 -6
  462. package/pennyfarthing_scripts/sprint/work.py +28 -7
  463. package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
  464. package/pennyfarthing_scripts/story/__init__.py +14 -16
  465. package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
  466. package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
  467. package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
  468. package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
  469. package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
  470. package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
  471. package/pennyfarthing_scripts/story/size.py +0 -1
  472. package/pennyfarthing_scripts/story/template.py +0 -1
  473. package/pennyfarthing_scripts/swebench.py +1 -2
  474. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  475. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  476. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  477. package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
  478. package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
  479. package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
  480. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  481. package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
  482. package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
  483. package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
  484. package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
  485. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  486. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
  487. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  488. package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
  489. package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
  490. package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
  491. package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
  492. package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
  493. package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
  494. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
  495. package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
  496. package/pennyfarthing_scripts/tests/conftest.py +1 -2
  497. package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
  498. package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
  499. package/pennyfarthing_scripts/tests/test_codemarkers.py +687 -0
  500. package/pennyfarthing_scripts/tests/test_common.py +9 -4
  501. package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
  502. package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
  503. package/pennyfarthing_scripts/tests/test_healthscore.py +516 -0
  504. package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
  505. package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
  506. package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
  507. package/pennyfarthing_scripts/tests/test_prime.py +39 -21
  508. package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
  509. package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
  510. package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
  511. package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
  512. package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
  513. package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
  514. package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
  515. package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
  516. package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
  517. package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
  518. package/pennyfarthing_scripts/theme/__init__.py +5 -0
  519. package/pennyfarthing_scripts/theme/__main__.py +6 -0
  520. package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  521. package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
  522. package/pennyfarthing_scripts/theme/cli.py +287 -0
  523. package/pennyfarthing_scripts/validate/__init__.py +21 -0
  524. package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  525. package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
  526. package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
  527. package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
  528. package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
  529. package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
  530. package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
  531. package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
  532. package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
  533. package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
  534. package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
  535. package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
  536. package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
  537. package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
  538. package/pennyfarthing_scripts/validate/cli.py +141 -0
  539. package/pennyfarthing_scripts/welcome_hook.py +2 -3
  540. package/pennyfarthing_scripts/workflow.py +3 -3
  541. package/scripts/README.md +41 -0
  542. package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
  543. package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
  544. package/pennyfarthing-dist/commands/benchmark.md +0 -485
  545. package/pennyfarthing-dist/commands/job-fair.md +0 -102
  546. package/pennyfarthing-dist/commands/solo.md +0 -447
  547. package/pennyfarthing-dist/guides/benchmarks.md +0 -62
  548. package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
  549. package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
  550. package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
  551. package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
  552. package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
  553. package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
  554. package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
  555. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
  556. package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
  557. package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
  558. package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
  559. package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
  560. package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
  561. package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
  562. package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
  563. package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
  564. package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
  565. package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
  566. package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
  567. package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
  568. package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
  569. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  570. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  571. package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
  572. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  573. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  574. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  575. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  576. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  577. package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
  578. package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
  579. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  580. package/pennyfarthing_scripts/tests/__pycache__/test_workflow_cli.cpython-314-pytest-9.0.2.pyc +0 -0
@@ -0,0 +1,718 @@
1
+ /**
2
+ * Tests for Story 95-5: Tool-watch Observation Scope
3
+ *
4
+ * RED state tests for tool call observation in the backseat agent.
5
+ * These tests cover all acceptance criteria:
6
+ *
7
+ * AC1: Backseat receives tool call data (name, params, result) from primary agent
8
+ * AC2: Data delivered within one tool-use cycle (processToolCall returns promptly)
9
+ * AC3: Large results truncated to configurable max size
10
+ * AC4: Truncation indicator included when results are truncated
11
+ * AC5: Non-blocking to primary agent (hook execution remains fast)
12
+ * AC6: Observations include analysis, not just raw tool call replay
13
+ * AC7: Context accumulates across tool calls
14
+ *
15
+ * Run with: npm test
16
+ */
17
+ import { describe, it, beforeEach, afterEach } from 'node:test';
18
+ import assert from 'node:assert';
19
+ import { mkdirSync, rmSync, existsSync, writeFileSync, readFileSync } from 'node:fs';
20
+ import { join, dirname } from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+ // Import the module under test (does not exist yet — will cause import failure)
23
+ import { processToolCall, readToolCalls, truncateResult, startToolWatcher, stopToolWatcher, } from './tool-watch.js';
24
+ // Import observation writer for integration tests
25
+ import { initObservationFile, parseObservationFile, } from './observation-writer.js';
26
+ // Get directory for test fixtures
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+ const TEST_DIR = join(__dirname, '__test_tool_watch__');
29
+ const SESSION_DIR = join(TEST_DIR, '.session');
30
+ // =============================================================================
31
+ // Test Fixtures
32
+ // =============================================================================
33
+ const DEFAULT_POLL_MS = 100; // Fast for testing
34
+ const DEFAULT_CONFIG = {
35
+ sessionDir: SESSION_DIR,
36
+ storyId: '95-5',
37
+ agent: 'architect',
38
+ persona: 'Will Bailey',
39
+ phase: 'implement',
40
+ pollIntervalMs: DEFAULT_POLL_MS,
41
+ maxResultSize: 500,
42
+ };
43
+ const OBS_CONFIG = {
44
+ storyId: '95-5',
45
+ agent: 'architect',
46
+ persona: 'Will Bailey',
47
+ phase: 'implement',
48
+ sessionDir: SESSION_DIR,
49
+ };
50
+ function toolCallsPath() {
51
+ return join(SESSION_DIR, `${DEFAULT_CONFIG.storyId}-tandem-toolcalls.jsonl`);
52
+ }
53
+ // =============================================================================
54
+ // Setup / Teardown
55
+ // =============================================================================
56
+ describe('95-5: Tool-watch Observation Scope', () => {
57
+ beforeEach(() => {
58
+ if (existsSync(TEST_DIR)) {
59
+ rmSync(TEST_DIR, { recursive: true });
60
+ }
61
+ mkdirSync(SESSION_DIR, { recursive: true });
62
+ });
63
+ afterEach(() => {
64
+ if (existsSync(TEST_DIR)) {
65
+ rmSync(TEST_DIR, { recursive: true });
66
+ }
67
+ });
68
+ // ===========================================================================
69
+ // AC1: Backseat receives tool call data (name, params, result)
70
+ // ===========================================================================
71
+ describe('AC1: Receives tool call data', () => {
72
+ it('should write tool call entry to JSONL transport file', () => {
73
+ const result = processToolCall({
74
+ sessionDir: SESSION_DIR,
75
+ storyId: '95-5',
76
+ toolName: 'Bash',
77
+ params: { command: 'npm test' },
78
+ toolResult: 'All 42 tests passed',
79
+ });
80
+ assert.ok(result.success, `processToolCall failed: ${result.error}`);
81
+ assert.ok(existsSync(toolCallsPath()), 'JSONL transport file should be created');
82
+ });
83
+ it('should include tool name in the entry', () => {
84
+ processToolCall({
85
+ sessionDir: SESSION_DIR,
86
+ storyId: '95-5',
87
+ toolName: 'Read',
88
+ params: { file_path: '/src/app.ts' },
89
+ toolResult: 'file contents here',
90
+ });
91
+ const calls = readToolCalls(toolCallsPath());
92
+ assert.ok(calls.success);
93
+ assert.ok(calls.data);
94
+ assert.ok(calls.data.length > 0);
95
+ assert.strictEqual(calls.data[0].toolName, 'Read');
96
+ });
97
+ it('should include params in the entry', () => {
98
+ processToolCall({
99
+ sessionDir: SESSION_DIR,
100
+ storyId: '95-5',
101
+ toolName: 'Bash',
102
+ params: { command: 'git status' },
103
+ toolResult: 'nothing to commit',
104
+ });
105
+ const calls = readToolCalls(toolCallsPath());
106
+ assert.ok(calls.success);
107
+ assert.ok(calls.data);
108
+ assert.deepStrictEqual(calls.data[0].params, { command: 'git status' });
109
+ });
110
+ it('should include result preview in the entry', () => {
111
+ processToolCall({
112
+ sessionDir: SESSION_DIR,
113
+ storyId: '95-5',
114
+ toolName: 'Bash',
115
+ params: { command: 'npm test' },
116
+ toolResult: 'All 42 tests passed',
117
+ });
118
+ const calls = readToolCalls(toolCallsPath());
119
+ assert.ok(calls.success);
120
+ assert.ok(calls.data);
121
+ assert.ok(calls.data[0].resultPreview.includes('All 42 tests passed'));
122
+ });
123
+ it('should include timestamp in the entry', () => {
124
+ processToolCall({
125
+ sessionDir: SESSION_DIR,
126
+ storyId: '95-5',
127
+ toolName: 'Bash',
128
+ params: { command: 'echo hi' },
129
+ toolResult: 'hi',
130
+ });
131
+ const calls = readToolCalls(toolCallsPath());
132
+ assert.ok(calls.success);
133
+ assert.ok(calls.data);
134
+ assert.ok(calls.data[0].timestamp, 'Entry should have a timestamp');
135
+ // ISO timestamp format
136
+ assert.ok(calls.data[0].timestamp.match(/^\d{4}-\d{2}-\d{2}T/), 'Timestamp should be ISO format');
137
+ });
138
+ it('should append multiple tool calls as separate JSONL lines', () => {
139
+ processToolCall({
140
+ sessionDir: SESSION_DIR,
141
+ storyId: '95-5',
142
+ toolName: 'Read',
143
+ params: { file_path: '/a.ts' },
144
+ toolResult: 'content a',
145
+ });
146
+ processToolCall({
147
+ sessionDir: SESSION_DIR,
148
+ storyId: '95-5',
149
+ toolName: 'Edit',
150
+ params: { file_path: '/a.ts', old_string: 'x', new_string: 'y' },
151
+ toolResult: 'success',
152
+ });
153
+ processToolCall({
154
+ sessionDir: SESSION_DIR,
155
+ storyId: '95-5',
156
+ toolName: 'Bash',
157
+ params: { command: 'npm test' },
158
+ toolResult: 'passed',
159
+ });
160
+ const calls = readToolCalls(toolCallsPath());
161
+ assert.ok(calls.success);
162
+ assert.ok(calls.data);
163
+ assert.strictEqual(calls.data.length, 3, `Expected 3 entries, got ${calls.data.length}`);
164
+ });
165
+ it('should include result_size field with original byte count', () => {
166
+ const bigResult = 'x'.repeat(2000);
167
+ processToolCall({
168
+ sessionDir: SESSION_DIR,
169
+ storyId: '95-5',
170
+ toolName: 'Bash',
171
+ params: { command: 'cat big.txt' },
172
+ toolResult: bigResult,
173
+ });
174
+ const calls = readToolCalls(toolCallsPath());
175
+ assert.ok(calls.success);
176
+ assert.ok(calls.data);
177
+ assert.strictEqual(calls.data[0].resultSize, 2000, 'resultSize should reflect original length');
178
+ });
179
+ });
180
+ // ===========================================================================
181
+ // AC2: Data delivered within one tool-use cycle
182
+ // ===========================================================================
183
+ describe('AC2: Delivered within one tool-use cycle', () => {
184
+ it('processToolCall should complete within 50ms', () => {
185
+ const start = Date.now();
186
+ const result = processToolCall({
187
+ sessionDir: SESSION_DIR,
188
+ storyId: '95-5',
189
+ toolName: 'Bash',
190
+ params: { command: 'npm test' },
191
+ toolResult: 'All tests passed',
192
+ });
193
+ const elapsed = Date.now() - start;
194
+ assert.ok(result.success);
195
+ assert.ok(elapsed < 50, `processToolCall took ${elapsed}ms, expected < 50ms`);
196
+ });
197
+ it('processToolCall should be synchronous (file written immediately)', () => {
198
+ processToolCall({
199
+ sessionDir: SESSION_DIR,
200
+ storyId: '95-5',
201
+ toolName: 'Bash',
202
+ params: { command: 'echo hello' },
203
+ toolResult: 'hello',
204
+ });
205
+ // File should exist immediately — no async delay
206
+ assert.ok(existsSync(toolCallsPath()), 'JSONL file should exist immediately after processToolCall');
207
+ const content = readFileSync(toolCallsPath(), 'utf-8').trim();
208
+ assert.ok(content.length > 0, 'File should have content immediately');
209
+ });
210
+ });
211
+ // ===========================================================================
212
+ // AC3: Large results truncated to configurable max size
213
+ // ===========================================================================
214
+ describe('AC3: Large results truncated', () => {
215
+ it('should truncate results exceeding maxResultSize', () => {
216
+ const bigResult = 'A'.repeat(1000);
217
+ const truncated = truncateResult(bigResult, 500);
218
+ assert.ok(truncated.length <= 600, `Truncated should be near max, got ${truncated.length}`);
219
+ assert.ok(!truncated.includes('A'.repeat(1000)), 'Should not contain full original');
220
+ });
221
+ it('should not truncate results under maxResultSize', () => {
222
+ const smallResult = 'small output';
223
+ const truncated = truncateResult(smallResult, 500);
224
+ assert.strictEqual(truncated, smallResult, 'Small results should pass through unchanged');
225
+ });
226
+ it('should respect custom maxResultSize', () => {
227
+ const result = 'B'.repeat(200);
228
+ const truncated100 = truncateResult(result, 100);
229
+ const truncated300 = truncateResult(result, 300);
230
+ assert.ok(truncated100.length < truncated300.length, 'Smaller max should produce shorter output');
231
+ });
232
+ it('should use configurable maxResultSize in processToolCall', () => {
233
+ const bigResult = 'C'.repeat(2000);
234
+ processToolCall({
235
+ sessionDir: SESSION_DIR,
236
+ storyId: '95-5',
237
+ toolName: 'Bash',
238
+ params: { command: 'cat huge.log' },
239
+ toolResult: bigResult,
240
+ maxResultSize: 200,
241
+ });
242
+ const calls = readToolCalls(toolCallsPath());
243
+ assert.ok(calls.success);
244
+ assert.ok(calls.data);
245
+ // Preview should be truncated to approximately 200 chars + indicator
246
+ assert.ok(calls.data[0].resultPreview.length < 300, `Preview should be near maxResultSize, got ${calls.data[0].resultPreview.length}`);
247
+ });
248
+ it('should default maxResultSize to 500 when not specified', () => {
249
+ const bigResult = 'D'.repeat(2000);
250
+ processToolCall({
251
+ sessionDir: SESSION_DIR,
252
+ storyId: '95-5',
253
+ toolName: 'Read',
254
+ params: { file_path: '/big.txt' },
255
+ toolResult: bigResult,
256
+ // No maxResultSize — should use default 500
257
+ });
258
+ const calls = readToolCalls(toolCallsPath());
259
+ assert.ok(calls.success);
260
+ assert.ok(calls.data);
261
+ assert.ok(calls.data[0].resultPreview.length < 600, `Default truncation to ~500, got ${calls.data[0].resultPreview.length}`);
262
+ });
263
+ });
264
+ // ===========================================================================
265
+ // AC4: Truncation indicator included
266
+ // ===========================================================================
267
+ describe('AC4: Truncation indicator', () => {
268
+ it('should include truncation indicator when result is truncated', () => {
269
+ const bigResult = 'E'.repeat(1000);
270
+ const truncated = truncateResult(bigResult, 500);
271
+ assert.ok(truncated.includes('[truncated from'), `Should include truncation indicator, got: "${truncated.slice(-60)}"`);
272
+ });
273
+ it('should include original size in truncation indicator', () => {
274
+ const bigResult = 'F'.repeat(1234);
275
+ const truncated = truncateResult(bigResult, 500);
276
+ assert.ok(truncated.includes('1234'), `Should include original char count "1234", got: "${truncated.slice(-60)}"`);
277
+ });
278
+ it('should include "chars" in truncation indicator', () => {
279
+ const bigResult = 'G'.repeat(800);
280
+ const truncated = truncateResult(bigResult, 500);
281
+ assert.ok(truncated.includes('chars'), `Should include "chars" in indicator, got: "${truncated.slice(-60)}"`);
282
+ });
283
+ it('should NOT include truncation indicator for small results', () => {
284
+ const smallResult = 'small output';
285
+ const truncated = truncateResult(smallResult, 500);
286
+ assert.ok(!truncated.includes('[truncated'), 'Small results should not have truncation indicator');
287
+ });
288
+ it('processToolCall should store truncation indicator in JSONL', () => {
289
+ const bigResult = 'H'.repeat(2000);
290
+ processToolCall({
291
+ sessionDir: SESSION_DIR,
292
+ storyId: '95-5',
293
+ toolName: 'Bash',
294
+ params: { command: 'cat huge.txt' },
295
+ toolResult: bigResult,
296
+ maxResultSize: 500,
297
+ });
298
+ const calls = readToolCalls(toolCallsPath());
299
+ assert.ok(calls.success);
300
+ assert.ok(calls.data);
301
+ assert.ok(calls.data[0].resultPreview.includes('[truncated from'), 'JSONL entry should contain truncation indicator');
302
+ });
303
+ });
304
+ // ===========================================================================
305
+ // AC5: Non-blocking to primary agent
306
+ // ===========================================================================
307
+ describe('AC5: Non-blocking', () => {
308
+ it('processToolCall with large result should still complete within 50ms', () => {
309
+ const bigResult = 'I'.repeat(100000); // 100KB
310
+ const start = Date.now();
311
+ const result = processToolCall({
312
+ sessionDir: SESSION_DIR,
313
+ storyId: '95-5',
314
+ toolName: 'Read',
315
+ params: { file_path: '/huge.ts' },
316
+ toolResult: bigResult,
317
+ });
318
+ const elapsed = Date.now() - start;
319
+ assert.ok(result.success);
320
+ assert.ok(elapsed < 50, `processToolCall with large result took ${elapsed}ms, expected < 50ms`);
321
+ });
322
+ it('processToolCall should not throw on session dir missing', () => {
323
+ const result = processToolCall({
324
+ sessionDir: '/nonexistent/session/dir',
325
+ storyId: '95-5',
326
+ toolName: 'Bash',
327
+ params: { command: 'echo hi' },
328
+ toolResult: 'hi',
329
+ });
330
+ // Should return error result, not throw
331
+ assert.strictEqual(result.success, false);
332
+ assert.ok(result.error, 'Should have error message');
333
+ });
334
+ it('startToolWatcher should return handle immediately', async () => {
335
+ // Create JSONL file first
336
+ writeFileSync(toolCallsPath(), '');
337
+ const start = Date.now();
338
+ const result = await startToolWatcher({
339
+ ...DEFAULT_CONFIG,
340
+ });
341
+ const elapsed = Date.now() - start;
342
+ assert.ok(result.success, `startToolWatcher failed: ${result.error}`);
343
+ assert.ok(result.data, 'Should return a handle');
344
+ assert.ok(elapsed < 1000, `startToolWatcher took ${elapsed}ms, expected < 1000ms`);
345
+ if (result.data) {
346
+ await stopToolWatcher(result.data);
347
+ }
348
+ });
349
+ });
350
+ // ===========================================================================
351
+ // AC6: Observations include analysis, not just raw tool call replay
352
+ // ===========================================================================
353
+ describe('AC6: Observations include analysis', () => {
354
+ it('should write observations with tool-watch trigger type', async () => {
355
+ const initResult = initObservationFile(OBS_CONFIG);
356
+ assert.ok(initResult.success);
357
+ assert.ok(initResult.data);
358
+ // Write some tool calls to the JSONL file
359
+ processToolCall({
360
+ sessionDir: SESSION_DIR,
361
+ storyId: '95-5',
362
+ toolName: 'Bash',
363
+ params: { command: 'npm test' },
364
+ toolResult: 'All 42 tests passed',
365
+ });
366
+ // Start watcher with observation file
367
+ const watchResult = await startToolWatcher({
368
+ ...DEFAULT_CONFIG,
369
+ observationFilePath: initResult.data.path,
370
+ });
371
+ assert.ok(watchResult.success);
372
+ assert.ok(watchResult.data);
373
+ // Wait for at least one poll cycle
374
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
375
+ await stopToolWatcher(watchResult.data);
376
+ // Parse observation file
377
+ const parsed = parseObservationFile(initResult.data.path);
378
+ assert.ok(parsed.success);
379
+ assert.ok(parsed.data);
380
+ assert.ok(parsed.data.entries.length > 0, 'Should have at least one observation entry');
381
+ const entry = parsed.data.entries[0];
382
+ assert.strictEqual(entry.triggerType, 'tool-watch', 'Trigger type should be tool-watch');
383
+ });
384
+ it('should include tool name in trigger detail', async () => {
385
+ const initResult = initObservationFile(OBS_CONFIG);
386
+ assert.ok(initResult.success);
387
+ assert.ok(initResult.data);
388
+ processToolCall({
389
+ sessionDir: SESSION_DIR,
390
+ storyId: '95-5',
391
+ toolName: 'Bash',
392
+ params: { command: 'npm test -- --filter notification' },
393
+ toolResult: 'Tests passed',
394
+ });
395
+ const watchResult = await startToolWatcher({
396
+ ...DEFAULT_CONFIG,
397
+ observationFilePath: initResult.data.path,
398
+ });
399
+ assert.ok(watchResult.success);
400
+ assert.ok(watchResult.data);
401
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
402
+ await stopToolWatcher(watchResult.data);
403
+ const parsed = parseObservationFile(initResult.data.path);
404
+ assert.ok(parsed.success);
405
+ assert.ok(parsed.data);
406
+ assert.ok(parsed.data.entries.length > 0);
407
+ const entry = parsed.data.entries[0];
408
+ assert.ok(entry.triggerDetail.includes('Bash'), `Trigger detail should include tool name, got: "${entry.triggerDetail}"`);
409
+ });
410
+ it('observation text should not be identical to raw tool result', async () => {
411
+ const initResult = initObservationFile(OBS_CONFIG);
412
+ assert.ok(initResult.success);
413
+ assert.ok(initResult.data);
414
+ const rawResult = 'PASS src/components/Header.test.tsx\n 42 tests passed';
415
+ processToolCall({
416
+ sessionDir: SESSION_DIR,
417
+ storyId: '95-5',
418
+ toolName: 'Bash',
419
+ params: { command: 'npm test' },
420
+ toolResult: rawResult,
421
+ });
422
+ const watchResult = await startToolWatcher({
423
+ ...DEFAULT_CONFIG,
424
+ observationFilePath: initResult.data.path,
425
+ });
426
+ assert.ok(watchResult.success);
427
+ assert.ok(watchResult.data);
428
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
429
+ await stopToolWatcher(watchResult.data);
430
+ const parsed = parseObservationFile(initResult.data.path);
431
+ assert.ok(parsed.success);
432
+ assert.ok(parsed.data);
433
+ assert.ok(parsed.data.entries.length > 0);
434
+ // Observation should contain more than just the raw result — it should
435
+ // be enriched (tool name + params summary + result analysis)
436
+ const entry = parsed.data.entries[0];
437
+ assert.ok(entry.observation.length > rawResult.length * 0.5, 'Observation should contain substantive content beyond just the raw result');
438
+ // Should not be an exact copy of the raw result
439
+ assert.notStrictEqual(entry.observation, rawResult, 'Observation should not be identical to raw tool result');
440
+ });
441
+ });
442
+ // ===========================================================================
443
+ // AC7: Context accumulates across tool calls
444
+ // ===========================================================================
445
+ describe('AC7: Context accumulates across tool calls', () => {
446
+ it('should process multiple tool calls into multiple observations', async () => {
447
+ const initResult = initObservationFile(OBS_CONFIG);
448
+ assert.ok(initResult.success);
449
+ assert.ok(initResult.data);
450
+ // Write several tool calls
451
+ processToolCall({
452
+ sessionDir: SESSION_DIR,
453
+ storyId: '95-5',
454
+ toolName: 'Read',
455
+ params: { file_path: '/src/app.ts' },
456
+ toolResult: 'export const app = {};',
457
+ });
458
+ processToolCall({
459
+ sessionDir: SESSION_DIR,
460
+ storyId: '95-5',
461
+ toolName: 'Edit',
462
+ params: { file_path: '/src/app.ts', old_string: '{}', new_string: '{ name: "test" }' },
463
+ toolResult: 'success',
464
+ });
465
+ processToolCall({
466
+ sessionDir: SESSION_DIR,
467
+ storyId: '95-5',
468
+ toolName: 'Bash',
469
+ params: { command: 'npm test' },
470
+ toolResult: 'All tests passed',
471
+ });
472
+ const watchResult = await startToolWatcher({
473
+ ...DEFAULT_CONFIG,
474
+ observationFilePath: initResult.data.path,
475
+ });
476
+ assert.ok(watchResult.success);
477
+ assert.ok(watchResult.data);
478
+ // Wait long enough for watcher to process all entries
479
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 5));
480
+ await stopToolWatcher(watchResult.data);
481
+ const parsed = parseObservationFile(initResult.data.path);
482
+ assert.ok(parsed.success);
483
+ assert.ok(parsed.data);
484
+ assert.ok(parsed.data.entries.length >= 1, `Should have at least 1 observation for 3 tool calls, got ${parsed.data.entries.length}`);
485
+ });
486
+ it('should track last-read position and not re-process old entries', async () => {
487
+ const initResult = initObservationFile(OBS_CONFIG);
488
+ assert.ok(initResult.success);
489
+ assert.ok(initResult.data);
490
+ // Write initial tool call
491
+ processToolCall({
492
+ sessionDir: SESSION_DIR,
493
+ storyId: '95-5',
494
+ toolName: 'Bash',
495
+ params: { command: 'echo first' },
496
+ toolResult: 'first',
497
+ });
498
+ const watchResult = await startToolWatcher({
499
+ ...DEFAULT_CONFIG,
500
+ observationFilePath: initResult.data.path,
501
+ });
502
+ assert.ok(watchResult.success);
503
+ assert.ok(watchResult.data);
504
+ // Wait for first batch to be processed
505
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
506
+ // Write a second tool call
507
+ processToolCall({
508
+ sessionDir: SESSION_DIR,
509
+ storyId: '95-5',
510
+ toolName: 'Bash',
511
+ params: { command: 'echo second' },
512
+ toolResult: 'second',
513
+ });
514
+ // Wait for second batch
515
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
516
+ await stopToolWatcher(watchResult.data);
517
+ const parsed = parseObservationFile(initResult.data.path);
518
+ assert.ok(parsed.success);
519
+ assert.ok(parsed.data);
520
+ // Count observations that mention "first" vs "second"
521
+ const firstObs = parsed.data.entries.filter(e => e.triggerDetail.includes('first') || e.observation.includes('first'));
522
+ const secondObs = parsed.data.entries.filter(e => e.triggerDetail.includes('second') || e.observation.includes('second'));
523
+ // "first" should appear exactly once (not re-processed)
524
+ assert.ok(firstObs.length <= 1, `"first" tool call should not be re-processed, appeared in ${firstObs.length} observations`);
525
+ });
526
+ it('should handle tool calls arriving while watcher is running', async () => {
527
+ const initResult = initObservationFile(OBS_CONFIG);
528
+ assert.ok(initResult.success);
529
+ assert.ok(initResult.data);
530
+ // Start watcher before any tool calls
531
+ writeFileSync(toolCallsPath(), '');
532
+ const watchResult = await startToolWatcher({
533
+ ...DEFAULT_CONFIG,
534
+ observationFilePath: initResult.data.path,
535
+ });
536
+ assert.ok(watchResult.success);
537
+ assert.ok(watchResult.data);
538
+ // Now write tool calls while watcher is running
539
+ processToolCall({
540
+ sessionDir: SESSION_DIR,
541
+ storyId: '95-5',
542
+ toolName: 'Glob',
543
+ params: { pattern: '**/*.ts' },
544
+ toolResult: 'src/app.ts\nsrc/main.ts',
545
+ });
546
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
547
+ processToolCall({
548
+ sessionDir: SESSION_DIR,
549
+ storyId: '95-5',
550
+ toolName: 'Read',
551
+ params: { file_path: '/src/app.ts' },
552
+ toolResult: 'export default {};',
553
+ });
554
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 3));
555
+ await stopToolWatcher(watchResult.data);
556
+ const parsed = parseObservationFile(initResult.data.path);
557
+ assert.ok(parsed.success);
558
+ assert.ok(parsed.data);
559
+ assert.ok(parsed.data.entries.length >= 1, 'Should observe tool calls that arrive while watcher is running');
560
+ });
561
+ });
562
+ // ===========================================================================
563
+ // Result objects per framework pattern
564
+ // ===========================================================================
565
+ describe('Result objects', () => {
566
+ it('processToolCall should return {success, error?}', () => {
567
+ const result = processToolCall({
568
+ sessionDir: SESSION_DIR,
569
+ storyId: '95-5',
570
+ toolName: 'Bash',
571
+ params: { command: 'echo hi' },
572
+ toolResult: 'hi',
573
+ });
574
+ assert.ok(typeof result === 'object');
575
+ assert.ok('success' in result, 'Result must have success field');
576
+ });
577
+ it('readToolCalls should return {success, data?, error?}', () => {
578
+ writeFileSync(toolCallsPath(), '');
579
+ const result = readToolCalls(toolCallsPath());
580
+ assert.ok(typeof result === 'object');
581
+ assert.ok('success' in result, 'Result must have success field');
582
+ });
583
+ it('readToolCalls should return error for non-existent file', () => {
584
+ const result = readToolCalls('/nonexistent/path/toolcalls.jsonl');
585
+ assert.strictEqual(result.success, false);
586
+ assert.ok(result.error, 'Should have error message');
587
+ });
588
+ it('startToolWatcher should return {success, data?: ToolWatchHandle, error?}', async () => {
589
+ writeFileSync(toolCallsPath(), '');
590
+ const result = await startToolWatcher(DEFAULT_CONFIG);
591
+ assert.ok(typeof result === 'object');
592
+ assert.ok('success' in result);
593
+ if (result.success && result.data) {
594
+ assert.ok(typeof result.data === 'object');
595
+ await stopToolWatcher(result.data);
596
+ }
597
+ });
598
+ it('stopToolWatcher should return {success, error?}', async () => {
599
+ writeFileSync(toolCallsPath(), '');
600
+ const watchResult = await startToolWatcher(DEFAULT_CONFIG);
601
+ assert.ok(watchResult.success);
602
+ assert.ok(watchResult.data);
603
+ const stopResult = await stopToolWatcher(watchResult.data);
604
+ assert.ok(typeof stopResult === 'object');
605
+ assert.ok('success' in stopResult);
606
+ assert.ok(stopResult.success);
607
+ });
608
+ });
609
+ // ===========================================================================
610
+ // Error resilience
611
+ // ===========================================================================
612
+ describe('Error resilience', () => {
613
+ it('processToolCall should handle missing session dir gracefully', () => {
614
+ const result = processToolCall({
615
+ sessionDir: '/nonexistent/session/dir',
616
+ storyId: '95-5',
617
+ toolName: 'Bash',
618
+ params: { command: 'echo hi' },
619
+ toolResult: 'hi',
620
+ });
621
+ assert.strictEqual(result.success, false);
622
+ assert.ok(result.error);
623
+ });
624
+ it('watcher should continue polling after observation write error', async () => {
625
+ // Write a tool call first
626
+ processToolCall({
627
+ sessionDir: SESSION_DIR,
628
+ storyId: '95-5',
629
+ toolName: 'Bash',
630
+ params: { command: 'echo test' },
631
+ toolResult: 'test',
632
+ });
633
+ // Start watcher with bad observation file path
634
+ const watchResult = await startToolWatcher({
635
+ ...DEFAULT_CONFIG,
636
+ observationFilePath: '/nonexistent/obs.md',
637
+ });
638
+ assert.ok(watchResult.success, 'Watcher should start even with bad observation path');
639
+ assert.ok(watchResult.data);
640
+ // Wait for poll cycles — watcher should NOT crash
641
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLL_MS * 5));
642
+ // Watcher should still be alive
643
+ const stopResult = await stopToolWatcher(watchResult.data);
644
+ assert.ok(stopResult.success, 'Should stop cleanly even after write errors');
645
+ });
646
+ it('stopToolWatcher should be idempotent', async () => {
647
+ writeFileSync(toolCallsPath(), '');
648
+ const watchResult = await startToolWatcher(DEFAULT_CONFIG);
649
+ assert.ok(watchResult.success);
650
+ assert.ok(watchResult.data);
651
+ const stop1 = await stopToolWatcher(watchResult.data);
652
+ assert.ok(stop1.success);
653
+ const stop2 = await stopToolWatcher(watchResult.data);
654
+ assert.ok(stop2.success, 'Second stop should also succeed (idempotent)');
655
+ });
656
+ it('readToolCalls should handle malformed JSONL lines gracefully', () => {
657
+ writeFileSync(toolCallsPath(), 'not json\n{"toolName":"Bash"}\nalso not json\n');
658
+ const result = readToolCalls(toolCallsPath());
659
+ assert.ok(result.success, 'Should succeed even with malformed lines');
660
+ assert.ok(result.data);
661
+ // Should parse what it can and skip bad lines
662
+ assert.ok(result.data.length >= 1, 'Should parse at least the valid line');
663
+ });
664
+ });
665
+ // ===========================================================================
666
+ // Edge cases
667
+ // ===========================================================================
668
+ describe('Edge cases', () => {
669
+ it('should handle empty tool result', () => {
670
+ const result = processToolCall({
671
+ sessionDir: SESSION_DIR,
672
+ storyId: '95-5',
673
+ toolName: 'Edit',
674
+ params: { file_path: '/a.ts', old_string: 'x', new_string: 'y' },
675
+ toolResult: '',
676
+ });
677
+ assert.ok(result.success);
678
+ const calls = readToolCalls(toolCallsPath());
679
+ assert.ok(calls.success);
680
+ assert.ok(calls.data);
681
+ assert.strictEqual(calls.data[0].resultPreview, '');
682
+ });
683
+ it('should handle tool result with special characters', () => {
684
+ const specialResult = 'Line 1\nLine 2\tTabbed\n"quoted" and {json: true}';
685
+ const result = processToolCall({
686
+ sessionDir: SESSION_DIR,
687
+ storyId: '95-5',
688
+ toolName: 'Bash',
689
+ params: { command: 'cat special.txt' },
690
+ toolResult: specialResult,
691
+ });
692
+ assert.ok(result.success);
693
+ const calls = readToolCalls(toolCallsPath());
694
+ assert.ok(calls.success);
695
+ assert.ok(calls.data);
696
+ // JSONL should properly escape special characters
697
+ assert.ok(calls.data[0].resultPreview.includes('Line 1'));
698
+ });
699
+ it('should handle empty JSONL file', () => {
700
+ writeFileSync(toolCallsPath(), '');
701
+ const result = readToolCalls(toolCallsPath());
702
+ assert.ok(result.success);
703
+ assert.ok(result.data);
704
+ assert.strictEqual(result.data.length, 0, 'Empty file should return empty array');
705
+ });
706
+ it('truncateResult should handle empty string', () => {
707
+ const result = truncateResult('', 500);
708
+ assert.strictEqual(result, '');
709
+ });
710
+ it('truncateResult should handle exactly maxResultSize chars', () => {
711
+ const exact = 'J'.repeat(500);
712
+ const result = truncateResult(exact, 500);
713
+ // Exactly at limit — should not truncate
714
+ assert.strictEqual(result, exact, 'Exactly at limit should not truncate');
715
+ });
716
+ });
717
+ });
718
+ //# sourceMappingURL=tool-watch.test.js.map