@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.
- package/README.md +19 -22
- package/package.json +17 -11
- package/packages/core/dist/cli/commands/doctor-file-layout.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +24 -0
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +346 -13
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js +1 -1
- package/packages/core/dist/cli/commands/e2e-fresh-install.test.js.map +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js +1 -1
- package/packages/core/dist/cli/commands/e2e-upgrade.test.js.map +1 -1
- package/packages/core/dist/cli/commands/hooks-consolidation.test.js +2 -2
- package/packages/core/dist/cli/commands/hooks-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts +7 -0
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +41 -8
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/uninstall.js +24 -13
- package/packages/core/dist/cli/commands/uninstall.js.map +1 -1
- package/packages/core/dist/cli/commands/update-consolidation.test.js +0 -10
- package/packages/core/dist/cli/commands/update-consolidation.test.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +26 -0
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/index.js +1 -1
- package/packages/core/dist/cli/index.js.map +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
- package/packages/core/dist/cli/theme-maker.test.js +64 -115
- package/packages/core/dist/cli/theme-maker.test.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +22 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/python.js +102 -0
- package/packages/core/dist/cli/utils/python.js.map +1 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +10 -0
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/index.d.ts +1 -1
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +2 -2
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/plugins/plugin-discovery.d.ts +116 -0
- package/packages/core/dist/plugins/plugin-discovery.d.ts.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.js +165 -0
- package/packages/core/dist/plugins/plugin-discovery.js.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.test.d.ts +22 -0
- package/packages/core/dist/plugins/plugin-discovery.test.d.ts.map +1 -0
- package/packages/core/dist/plugins/plugin-discovery.test.js +498 -0
- package/packages/core/dist/plugins/plugin-discovery.test.js.map +1 -0
- package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
- package/packages/core/dist/workflow/context-watch.d.ts +80 -0
- package/packages/core/dist/workflow/context-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/context-watch.js +235 -0
- package/packages/core/dist/workflow/context-watch.js.map +1 -0
- package/packages/core/dist/workflow/context-watch.test.d.ts +1 -0
- package/packages/core/dist/workflow/context-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/context-watch.test.js +746 -0
- package/packages/core/dist/workflow/context-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/file-watch.d.ts +82 -0
- package/packages/core/dist/workflow/file-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/file-watch.js +198 -0
- package/packages/core/dist/workflow/file-watch.js.map +1 -0
- package/packages/core/dist/workflow/file-watch.test.d.ts +21 -0
- package/packages/core/dist/workflow/file-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/file-watch.test.js +469 -0
- package/packages/core/dist/workflow/file-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/observation-writer.d.ts +79 -0
- package/packages/core/dist/workflow/observation-writer.d.ts.map +1 -0
- package/packages/core/dist/workflow/observation-writer.js +97 -0
- package/packages/core/dist/workflow/observation-writer.js.map +1 -0
- package/packages/core/dist/workflow/observation-writer.test.d.ts +18 -0
- package/packages/core/dist/workflow/observation-writer.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/observation-writer.test.js +424 -0
- package/packages/core/dist/workflow/observation-writer.test.js.map +1 -0
- package/packages/core/dist/workflow/story-workflow-routing.test.js +4 -2
- package/packages/core/dist/workflow/story-workflow-routing.test.js.map +1 -1
- package/packages/core/dist/workflow/tandem-lifecycle.d.ts +117 -0
- package/packages/core/dist/workflow/tandem-lifecycle.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.js +186 -0
- package/packages/core/dist/workflow/tandem-lifecycle.js.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts +16 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.js +531 -0
- package/packages/core/dist/workflow/tandem-lifecycle.test.js.map +1 -0
- package/packages/core/dist/workflow/tool-watch.d.ts +68 -0
- package/packages/core/dist/workflow/tool-watch.d.ts.map +1 -0
- package/packages/core/dist/workflow/tool-watch.js +166 -0
- package/packages/core/dist/workflow/tool-watch.js.map +1 -0
- package/packages/core/dist/workflow/tool-watch.test.d.ts +18 -0
- package/packages/core/dist/workflow/tool-watch.test.d.ts.map +1 -0
- package/packages/core/dist/workflow/tool-watch.test.js +718 -0
- package/packages/core/dist/workflow/tool-watch.test.js.map +1 -0
- package/packages/core/dist/workflow/workflow-migration.test.js +8 -4
- package/packages/core/dist/workflow/workflow-migration.test.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.d.ts +7 -0
- package/packages/core/dist/workflow/workflow-schema.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.js +44 -0
- package/packages/core/dist/workflow/workflow-schema.js.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.d.ts.map +1 -1
- package/packages/core/dist/workflow/workflow-schema.test.js +192 -0
- package/packages/core/dist/workflow/workflow-schema.test.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +1 -3
- package/pennyfarthing-dist/agents/architect.md +0 -6
- package/pennyfarthing-dist/agents/devops.md +0 -6
- package/pennyfarthing-dist/agents/handoff.md +18 -3
- package/pennyfarthing-dist/agents/orchestrator.md +0 -6
- package/pennyfarthing-dist/agents/pm.md +0 -6
- package/pennyfarthing-dist/agents/sm-finish.md +1 -1
- package/pennyfarthing-dist/agents/sm-handoff.md +27 -4
- package/pennyfarthing-dist/agents/sm.md +11 -11
- package/pennyfarthing-dist/agents/tandem-backseat.md +119 -0
- package/pennyfarthing-dist/commands/architect.md +11 -3
- package/pennyfarthing-dist/commands/close-epic.md +24 -131
- package/pennyfarthing-dist/commands/create-theme.md +14 -24
- package/pennyfarthing-dist/commands/dev.md +11 -3
- package/pennyfarthing-dist/commands/devops.md +11 -3
- package/pennyfarthing-dist/commands/health-check.md +1 -3
- package/pennyfarthing-dist/commands/help.md +8 -12
- package/pennyfarthing-dist/commands/list-themes.md +14 -16
- package/pennyfarthing-dist/commands/orchestrator.md +11 -3
- package/pennyfarthing-dist/commands/parallel-work.md +1 -3
- package/pennyfarthing-dist/commands/pm.md +11 -3
- package/pennyfarthing-dist/commands/prime.md +6 -6
- package/pennyfarthing-dist/commands/reviewer.md +11 -3
- package/pennyfarthing-dist/commands/run-ci.md +1 -1
- package/pennyfarthing-dist/commands/set-theme.md +14 -51
- package/pennyfarthing-dist/commands/setup.md +5 -1
- package/pennyfarthing-dist/commands/show-theme.md +14 -16
- package/pennyfarthing-dist/commands/sm.md +11 -3
- package/pennyfarthing-dist/commands/tea.md +11 -3
- package/pennyfarthing-dist/commands/tech-writer.md +11 -3
- package/pennyfarthing-dist/commands/theme-maker.md +14 -671
- package/pennyfarthing-dist/commands/theme.md +95 -0
- package/pennyfarthing-dist/commands/ux-designer.md +11 -3
- package/pennyfarthing-dist/commands/work.md +3 -5
- package/pennyfarthing-dist/guides/agent-behavior.md +62 -6
- package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
- package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
- package/pennyfarthing-dist/guides/bikelane.md +3 -2
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
- package/pennyfarthing-dist/guides/hooks.md +5 -5
- package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
- package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
- package/pennyfarthing-dist/guides/prime.md +2 -2
- package/pennyfarthing-dist/guides/scale-levels.md +4 -6
- package/pennyfarthing-dist/guides/skill-schema.md +4 -4
- package/pennyfarthing-dist/guides/tandem-protocol.md +158 -0
- package/pennyfarthing-dist/personas/themes/discworld.yaml +1 -1
- package/pennyfarthing-dist/personas/themes/fifth-element.yaml +295 -0
- package/pennyfarthing-dist/scripts/README.md +1 -1
- package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
- package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
- package/pennyfarthing-dist/scripts/core/handoff-marker.sh +0 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
- package/pennyfarthing-dist/scripts/git/release.sh +0 -0
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +131 -54
- package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +32 -15
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
- package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
- package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
- package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
- package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
- package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
- package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/statusline.sh +50 -8
- package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
- package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
- package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
- package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
- package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
- package/pennyfarthing-dist/scripts/theme/README.md +1 -1
- package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
- package/pennyfarthing-dist/scripts/workflow/README.md +2 -2
- package/pennyfarthing-dist/scripts/workflow/check.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +10 -144
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/skill-registry.schema.json +8 -0
- package/pennyfarthing-dist/skills/skill-registry.yaml +21 -17
- package/pennyfarthing-dist/skills/sprint/skill.md +25 -2
- package/pennyfarthing-dist/skills/story/scripts/create-story.sh +0 -0
- package/pennyfarthing-dist/skills/story/scripts/size-story.sh +0 -0
- package/pennyfarthing-dist/skills/story/scripts/story-template.sh +0 -0
- package/pennyfarthing-dist/skills/theme/skill.md +290 -75
- package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
- package/pennyfarthing-dist/skills/workflow/scripts/list-workflows.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/resume-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/show-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/start-workflow.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/scripts/workflow-status.sh +0 -0
- package/pennyfarthing-dist/skills/workflow/skill.md +27 -4
- package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
- package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
- package/pennyfarthing-dist/workflows/architecture/workflow.yaml +65 -0
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +70 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +41 -24
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +61 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/bellmode_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_bidirectional_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/patch_mode.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +202 -47
- package/pennyfarthing_scripts/brownfield/__init__.py +6 -6
- package/pennyfarthing_scripts/brownfield/__main__.py +1 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/cli.py +0 -1
- package/pennyfarthing_scripts/brownfield/discover.py +1 -2
- package/pennyfarthing_scripts/cli.py +23 -3
- package/pennyfarthing_scripts/codemarkers/__init__.py +23 -0
- package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/analyze.py +501 -0
- package/pennyfarthing_scripts/codemarkers/cli.py +179 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +88 -0
- package/pennyfarthing_scripts/codemarkers/models.py +60 -0
- package/pennyfarthing_scripts/common/__init__.py +8 -9
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +1 -1
- package/pennyfarthing_scripts/complexity/__init__.py +15 -0
- package/pennyfarthing_scripts/complexity/__main__.py +6 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/analyze.py +207 -0
- package/pennyfarthing_scripts/complexity/cli.py +82 -0
- package/pennyfarthing_scripts/complexity/formatters.py +64 -0
- package/pennyfarthing_scripts/complexity/models.py +32 -0
- package/pennyfarthing_scripts/context.py +14 -15
- package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/analyze.py +322 -0
- package/pennyfarthing_scripts/deadcode/cli.py +163 -0
- package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
- package/pennyfarthing_scripts/deadcode/models.py +54 -0
- package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
- package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
- package/pennyfarthing_scripts/dependencies/cli.py +76 -0
- package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
- package/pennyfarthing_scripts/dependencies/models.py +39 -0
- package/pennyfarthing_scripts/git/__init__.py +5 -5
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +3 -2
- package/pennyfarthing_scripts/git/status_all.py +1 -1
- package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
- package/pennyfarthing_scripts/healthscore/__main__.py +14 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/analyze.py +591 -0
- package/pennyfarthing_scripts/healthscore/cli.py +80 -0
- package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
- package/pennyfarthing_scripts/healthscore/models.py +43 -0
- package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing_scripts/hooks.py +8 -11
- package/pennyfarthing_scripts/hotspots/__init__.py +6 -6
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/analyze.py +155 -14
- package/pennyfarthing_scripts/hotspots/cli.py +12 -10
- package/pennyfarthing_scripts/hotspots/models.py +0 -1
- package/pennyfarthing_scripts/jira/__init__.py +15 -17
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +44 -18
- package/pennyfarthing_scripts/jira/claim.py +21 -0
- package/pennyfarthing_scripts/jira/cli.py +6 -3
- package/pennyfarthing_scripts/jira/client.py +32 -4
- package/pennyfarthing_scripts/jira/create.py +45 -1
- package/pennyfarthing_scripts/jira/epic.py +3 -2
- package/pennyfarthing_scripts/jira/reconcile.py +0 -1
- package/pennyfarthing_scripts/jira/story.py +2 -0
- package/pennyfarthing_scripts/jira/sync.py +1 -1
- package/pennyfarthing_scripts/migration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/skill.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/step.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/validate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/skill.py +0 -1
- package/pennyfarthing_scripts/migration/step.py +0 -1
- package/pennyfarthing_scripts/migration/validate.py +8 -5
- package/pennyfarthing_scripts/patch_mode.py +2 -2
- package/pennyfarthing_scripts/preflight/__init__.py +1 -1
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/finish.py +0 -1
- package/pennyfarthing_scripts/pretooluse_hook.py +6 -7
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +5 -1
- package/pennyfarthing_scripts/prime/loader.py +2 -3
- package/pennyfarthing_scripts/prime/persona.py +2 -1
- package/pennyfarthing_scripts/prime/tiers.py +4 -4
- package/pennyfarthing_scripts/schema_validation_hook.py +2 -3
- package/pennyfarthing_scripts/sprint/__init__.py +10 -12
- package/pennyfarthing_scripts/sprint/__main__.py +2 -2
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/import_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +0 -1
- package/pennyfarthing_scripts/sprint/archive_epic.py +198 -97
- package/pennyfarthing_scripts/sprint/cli.py +62 -46
- package/pennyfarthing_scripts/sprint/epic_add.py +8 -1
- package/pennyfarthing_scripts/sprint/import_epic.py +42 -18
- package/pennyfarthing_scripts/sprint/loader.py +6 -0
- package/pennyfarthing_scripts/sprint/status.py +1 -2
- package/pennyfarthing_scripts/sprint/story_add.py +202 -27
- package/pennyfarthing_scripts/sprint/story_finish.py +209 -0
- package/pennyfarthing_scripts/sprint/story_update.py +11 -3
- package/pennyfarthing_scripts/sprint/validate_cmd.py +0 -1
- package/pennyfarthing_scripts/sprint/validator.py +120 -6
- package/pennyfarthing_scripts/sprint/work.py +28 -7
- package/pennyfarthing_scripts/sprint/yaml_io.py +10 -2
- package/pennyfarthing_scripts/story/__init__.py +14 -16
- package/pennyfarthing_scripts/story/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/size.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/__pycache__/template.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/story/size.py +0 -1
- package/pennyfarthing_scripts/story/template.py +0 -1
- package/pennyfarthing_scripts/swebench.py +1 -2
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_cli_modules.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_common.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_jira_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_package_structure.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_patch_mode.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_tiers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_token_counting.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_workflow_check.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/conftest.py +1 -2
- package/pennyfarthing_scripts/tests/test_brownfield.py +10 -13
- package/pennyfarthing_scripts/tests/test_cli_modules.py +0 -4
- package/pennyfarthing_scripts/tests/test_codemarkers.py +687 -0
- package/pennyfarthing_scripts/tests/test_common.py +9 -4
- package/pennyfarthing_scripts/tests/test_epic_shard_validation.py +699 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +10 -13
- package/pennyfarthing_scripts/tests/test_healthscore.py +516 -0
- package/pennyfarthing_scripts/tests/test_jira_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_package_structure.py +3 -16
- package/pennyfarthing_scripts/tests/test_patch_mode.py +7 -11
- package/pennyfarthing_scripts/tests/test_prime.py +39 -21
- package/pennyfarthing_scripts/tests/test_sprint_package.py +3 -8
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +53 -5
- package/pennyfarthing_scripts/tests/test_story_add.py +3 -7
- package/pennyfarthing_scripts/tests/test_story_package.py +0 -3
- package/pennyfarthing_scripts/tests/test_story_update.py +5 -10
- package/pennyfarthing_scripts/tests/test_tiers.py +18 -17
- package/pennyfarthing_scripts/tests/test_token_counting.py +19 -13
- package/pennyfarthing_scripts/tests/test_validate_cmd.py +2 -7
- package/pennyfarthing_scripts/tests/test_workflow_check.py +0 -2
- package/pennyfarthing_scripts/tests/test_yaml_io.py +0 -3
- package/pennyfarthing_scripts/theme/__init__.py +5 -0
- package/pennyfarthing_scripts/theme/__main__.py +6 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/cli.py +287 -0
- package/pennyfarthing_scripts/validate/__init__.py +21 -0
- package/pennyfarthing_scripts/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__init__.py +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/agent.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/schema.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/skill_command.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/agent.py +239 -0
- package/pennyfarthing_scripts/validate/adapters/schema.py +30 -0
- package/pennyfarthing_scripts/validate/adapters/skill_command.py +292 -0
- package/pennyfarthing_scripts/validate/adapters/sprint.py +69 -0
- package/pennyfarthing_scripts/validate/adapters/workflow.py +320 -0
- package/pennyfarthing_scripts/validate/cli.py +141 -0
- package/pennyfarthing_scripts/welcome_hook.py +2 -3
- package/pennyfarthing_scripts/workflow.py +3 -3
- package/scripts/README.md +41 -0
- package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
- package/pennyfarthing-dist/commands/benchmark-control.md +0 -69
- package/pennyfarthing-dist/commands/benchmark.md +0 -485
- package/pennyfarthing-dist/commands/job-fair.md +0 -102
- package/pennyfarthing-dist/commands/solo.md +0 -447
- package/pennyfarthing-dist/guides/benchmarks.md +0 -62
- package/pennyfarthing-dist/scripts/hooks/__pycache__/question_reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/test/ensure-swebench-data.sh +0 -59
- package/pennyfarthing-dist/scripts/test/ground-truth-judge.py +0 -220
- package/pennyfarthing-dist/scripts/test/swebench-judge.py +0 -374
- package/pennyfarthing-dist/scripts/test/test-cache.sh +0 -165
- package/pennyfarthing-dist/scripts/test/test-setup.sh +0 -337
- package/pennyfarthing-dist/scripts/theme/compute-theme-tiers.sh +0 -13
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +0 -402
- package/pennyfarthing-dist/scripts/theme/update-theme-tiers.sh +0 -97
- package/pennyfarthing-dist/skills/finalize-run/SKILL.md +0 -261
- package/pennyfarthing-dist/skills/judge/SKILL.md +0 -644
- package/pennyfarthing-dist/skills/persona-benchmark/SKILL.md +0 -187
- package/pennyfarthing-dist/workflows/dev-story/checklist.md +0 -80
- package/pennyfarthing-dist/workflows/dev-story/instructions.xml +0 -410
- package/pennyfarthing-dist/workflows/dev-story/workflow.yaml +0 -50
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-01-understand.md +0 -201
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-02-investigate.md +0 -156
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-03-generate.md +0 -140
- package/pennyfarthing-dist/workflows/quick-spec/steps/step-04-review.md +0 -203
- package/pennyfarthing-dist/workflows/quick-spec/tech-spec-template.md +0 -74
- package/pennyfarthing-dist/workflows/quick-spec/workflow.yaml +0 -27
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/migration/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- 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
|