@pennyfarthing/core 11.1.0 → 11.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 +8 -8
- package/package.json +16 -14
- package/packages/core/dist/cli/utils/constants.d.ts +1 -1
- package/packages/core/dist/cli/utils/constants.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/constants.js +2 -1
- package/packages/core/dist/cli/utils/constants.js.map +1 -1
- package/packages/core/dist/consultation/dialogue-manager.d.ts +75 -0
- package/packages/core/dist/consultation/dialogue-manager.d.ts.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.js +334 -0
- package/packages/core/dist/consultation/dialogue-manager.js.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.test.d.ts +19 -0
- package/packages/core/dist/consultation/dialogue-manager.test.d.ts.map +1 -0
- package/packages/core/dist/consultation/dialogue-manager.test.js +444 -0
- package/packages/core/dist/consultation/dialogue-manager.test.js.map +1 -0
- package/packages/core/dist/server/api/git.d.ts +13 -1
- package/packages/core/dist/server/api/git.d.ts.map +1 -1
- package/packages/core/dist/server/api/git.js +53 -34
- package/packages/core/dist/server/api/git.js.map +1 -1
- package/packages/core/dist/server/otlp-receiver.d.ts +16 -11
- package/packages/core/dist/server/otlp-receiver.d.ts.map +1 -1
- package/packages/core/dist/server/otlp-receiver.js +185 -24
- package/packages/core/dist/server/otlp-receiver.js.map +1 -1
- package/packages/core/dist/server/otlp-receiver.test.d.ts +21 -0
- package/packages/core/dist/server/otlp-receiver.test.d.ts.map +1 -0
- package/packages/core/dist/server/otlp-receiver.test.js +446 -0
- package/packages/core/dist/server/otlp-receiver.test.js.map +1 -0
- package/packages/core/dist/shared/portrait-resolver.d.ts +9 -0
- package/packages/core/dist/shared/portrait-resolver.d.ts.map +1 -1
- package/packages/core/dist/shared/portrait-resolver.js +27 -0
- package/packages/core/dist/shared/portrait-resolver.js.map +1 -1
- package/packages/core/dist/shared/portrait-resolver.test.js +47 -1
- package/packages/core/dist/shared/portrait-resolver.test.js.map +1 -1
- package/packages/core/dist/shared/skill-search.test.js +2 -2
- package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts +13 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.d.ts.map +1 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.js +126 -0
- package/packages/core/dist/shared/tandem-portrait-inventory.test.js.map +1 -0
- package/pennyfarthing-dist/agents/dev.md +1 -1
- package/pennyfarthing-dist/agents/reviewer.md +1 -1
- package/pennyfarthing-dist/agents/sm-setup.md +1 -1
- package/pennyfarthing-dist/agents/sm.md +2 -2
- package/pennyfarthing-dist/agents/tea.md +1 -1
- package/pennyfarthing-dist/agents/testing-runner.md +2 -1
- package/pennyfarthing-dist/commands/pf-chore.md +2 -2
- package/pennyfarthing-dist/commands/pf-standalone.md +7 -2
- package/pennyfarthing-dist/guides/agent-behavior.md +1 -1
- package/pennyfarthing-dist/guides/agent-tag-taxonomy.md +1 -1
- package/pennyfarthing-dist/guides/bikerack.md +3 -3
- package/pennyfarthing-dist/guides/hooks.md +1 -1
- package/pennyfarthing-dist/guides/worktree-mode.md +3 -3
- package/pennyfarthing-dist/guides/xml-tags.md +2 -2
- package/pennyfarthing-dist/scripts/README.md +1 -1
- package/pennyfarthing-dist/scripts/core/agent-session.sh +0 -0
- package/pennyfarthing-dist/scripts/core/check-context.sh +1 -1
- package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +322 -0
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
- package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
- package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
- package/pennyfarthing-dist/scripts/git/README.md +24 -14
- package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +5 -266
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +5 -151
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +6 -144
- package/pennyfarthing-dist/scripts/git/release.sh +0 -0
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +5 -496
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/README.md +1 -1
- package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +1 -1
- 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/dispatcher-template.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +9 -11
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
- 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 +1 -1
- 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/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 +0 -0
- package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
- package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +76 -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 +0 -0
- 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 +0 -0
- 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/list-themes.sh +0 -0
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -0
- 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 +0 -0
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +4 -221
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +5 -13
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +4 -123
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +4 -33
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +4 -156
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +4 -131
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +4 -249
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +4 -160
- package/pennyfarthing-dist/skills/pf-bc/usage.md +1 -1
- package/pennyfarthing-dist/skills/pf-jira/examples.md +5 -2
- package/pennyfarthing-dist/skills/pf-story/scripts/create-story.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/size-story.sh +0 -0
- package/pennyfarthing-dist/skills/pf-story/scripts/story-template.sh +0 -0
- package/pennyfarthing-dist/skills/pf-workflow/examples.md +27 -16
- package/pennyfarthing-dist/skills/pf-workflow/skill.md +9 -12
- package/pennyfarthing-dist/skills/pf-workflow/usage.md +33 -8
- package/pennyfarthing-dist/workflows/bdd-tandem.yaml +18 -6
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +1 -1
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-04-verify.md +1 -1
- package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +1 -1
- package/pennyfarthing-dist/workflows/review-tandem.yaml +65 -0
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +16 -8
- package/pennyfarthing_scripts/CLAUDE.md +26 -4
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/pretooluse_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/session_start_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bc/cli.py +3 -5
- package/pennyfarthing_scripts/bikerack/__pycache__/background_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/changed_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/debug_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/diffs_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/portrait.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/progress_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/__pycache__/ws_client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bikerack/background_panel.py +86 -5
- package/pennyfarthing_scripts/bikerack/base_panel.py +62 -0
- package/pennyfarthing_scripts/bikerack/changed_panel.py +32 -28
- package/pennyfarthing_scripts/bikerack/cli.py +10 -11
- package/pennyfarthing_scripts/bikerack/debug_panel.py +31 -1
- package/pennyfarthing_scripts/bikerack/diffs_panel.py +74 -17
- package/pennyfarthing_scripts/bikerack/git_panel.py +103 -33
- package/pennyfarthing_scripts/bikerack/launcher.py +15 -15
- package/pennyfarthing_scripts/bikerack/progress_panel.py +315 -0
- package/pennyfarthing_scripts/bikerack/sprint_panel.py +158 -26
- package/pennyfarthing_scripts/bikerack/tui.py +336 -30
- package/pennyfarthing_scripts/bikerack/ws_client.py +2 -2
- package/pennyfarthing_scripts/cli.py +37 -65
- package/pennyfarthing_scripts/consultation/__init__.py +1 -0
- package/pennyfarthing_scripts/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/consultation/cli.py +149 -0
- package/pennyfarthing_scripts/consultation/dialogue_manager.py +417 -0
- package/pennyfarthing_scripts/context.py +3 -3
- package/pennyfarthing_scripts/epic/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/epic/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__init__.py +12 -1
- 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__/hooks_installer.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/repos.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/worktree.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +3 -4
- package/pennyfarthing_scripts/git/hooks_installer.py +152 -0
- package/pennyfarthing_scripts/git/repos.py +196 -0
- package/pennyfarthing_scripts/git/status_all.py +27 -11
- package/pennyfarthing_scripts/git/worktree.py +302 -0
- package/pennyfarthing_scripts/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git_group/cli.py +143 -40
- package/pennyfarthing_scripts/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/handoff/complete_phase.py +12 -0
- package/pennyfarthing_scripts/handoff/resolve_gate.py +5 -14
- package/pennyfarthing_scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
- package/pennyfarthing_scripts/hooks.py +3 -17
- package/pennyfarthing_scripts/pretooluse_hook.py +1 -1
- package/pennyfarthing_scripts/prime/__pycache__/heatmap.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/heatmap.py +655 -0
- package/pennyfarthing_scripts/session/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/session_start_hook.py +1 -1
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/loader.py +15 -1
- package/pennyfarthing_scripts/sprint/story_finish.py +14 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_cli.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_handoff_e2e.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/test_bikerack.py +51 -51
- package/pennyfarthing_scripts/tests/test_dialogue_manager.py +811 -0
- package/pennyfarthing_scripts/tests/test_handoff_cli.py +16 -11
- package/pennyfarthing_scripts/tests/test_workflow_check.py +2 -3
- package/pennyfarthing_scripts/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/validate/adapters/tandem_awareness.py +254 -0
- package/pennyfarthing_scripts/validate/cli.py +17 -5
- package/pennyfarthing_scripts/workflow/__init__.py +40 -0
- package/pennyfarthing_scripts/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/scale.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/workflow/cli.py +1099 -0
- package/pennyfarthing_scripts/workflow/helpers.py +241 -0
- package/pennyfarthing_scripts/{workflow.py → workflow/scale.py} +0 -104
- package/pennyfarthing_scripts/workflow/state.py +112 -0
- package/scripts/README.md +41 -0
- package/pennyfarthing-dist/skills/pf-workflow/scripts/list-workflows.sh +0 -91
- package/pennyfarthing-dist/skills/pf-workflow/scripts/resume-workflow.sh +0 -163
- package/pennyfarthing-dist/skills/pf-workflow/scripts/show-workflow.sh +0 -138
- package/pennyfarthing-dist/skills/pf-workflow/scripts/start-workflow.sh +0 -273
- package/pennyfarthing-dist/skills/pf-workflow/scripts/workflow-status.sh +0 -167
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared helpers for workflow commands.
|
|
3
|
+
|
|
4
|
+
Extracted from complete-step.py and bash scripts. Provides common
|
|
5
|
+
functions for session parsing, workflow file resolution, and step management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_workflows_dir(project_root: Path | None = None) -> Path:
|
|
19
|
+
"""Get the workflows directory path."""
|
|
20
|
+
root = project_root or get_project_root()
|
|
21
|
+
return root / ".pennyfarthing" / "workflows"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_session_dir(project_root: Path | None = None) -> Path:
|
|
25
|
+
"""Get the session directory path."""
|
|
26
|
+
root = project_root or get_project_root()
|
|
27
|
+
return root / ".session"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_workflow_file(workflows_dir: Path, workflow_name: str) -> Path | None:
|
|
31
|
+
"""Find workflow YAML definition.
|
|
32
|
+
|
|
33
|
+
Supports both flat (name.yaml) and nested (name/workflow.yaml) layouts.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Path to the workflow file, or None if not found.
|
|
37
|
+
"""
|
|
38
|
+
flat = workflows_dir / f"{workflow_name}.yaml"
|
|
39
|
+
if flat.exists():
|
|
40
|
+
return flat
|
|
41
|
+
|
|
42
|
+
nested = workflows_dir / workflow_name / "workflow.yaml"
|
|
43
|
+
if nested.exists():
|
|
44
|
+
return nested
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def load_workflow_data(workflow_file: Path) -> dict:
|
|
50
|
+
"""Load and parse workflow YAML file.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Parsed YAML data dict.
|
|
54
|
+
"""
|
|
55
|
+
with open(workflow_file) as f:
|
|
56
|
+
return yaml.safe_load(f) or {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_workflow_type(workflow_data: dict) -> str:
|
|
60
|
+
"""Get workflow type from parsed YAML data.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
'phased', 'stepped', or 'procedural'
|
|
64
|
+
"""
|
|
65
|
+
wf = workflow_data.get("workflow", {})
|
|
66
|
+
wf_type = wf.get("type", "phased")
|
|
67
|
+
has_steps = wf.get("steps") is not None
|
|
68
|
+
if has_steps or wf_type == "stepped":
|
|
69
|
+
return "stepped"
|
|
70
|
+
return wf_type
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve_steps_path(
|
|
74
|
+
workflow_data: dict,
|
|
75
|
+
workflow_dir: Path,
|
|
76
|
+
mode: str | None,
|
|
77
|
+
project_root: Path,
|
|
78
|
+
) -> Path:
|
|
79
|
+
"""Resolve the steps directory path from workflow data.
|
|
80
|
+
|
|
81
|
+
Handles mode-specific paths, relative paths, and absolute paths.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
workflow_data: Parsed workflow YAML
|
|
85
|
+
workflow_dir: Directory containing the workflow.yaml file
|
|
86
|
+
mode: Active mode (create, validate, edit) or None
|
|
87
|
+
project_root: Project root directory
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Resolved absolute Path to steps directory
|
|
91
|
+
"""
|
|
92
|
+
wf = workflow_data.get("workflow", {})
|
|
93
|
+
|
|
94
|
+
# Try mode-specific path first
|
|
95
|
+
if mode:
|
|
96
|
+
modes = wf.get("modes", {})
|
|
97
|
+
mode_path = modes.get(mode)
|
|
98
|
+
if mode_path and mode_path != "null":
|
|
99
|
+
return _resolve_path(mode_path, workflow_dir, project_root)
|
|
100
|
+
|
|
101
|
+
# Try default mode
|
|
102
|
+
default_mode = wf.get("modes", {}).get("default")
|
|
103
|
+
if default_mode:
|
|
104
|
+
modes = wf.get("modes", {})
|
|
105
|
+
mode_path = modes.get(default_mode)
|
|
106
|
+
if mode_path and mode_path != "null":
|
|
107
|
+
return _resolve_path(mode_path, workflow_dir, project_root)
|
|
108
|
+
|
|
109
|
+
# Fall back to steps.path
|
|
110
|
+
steps_path_str = wf.get("steps", {}).get("path", ".")
|
|
111
|
+
return _resolve_path(steps_path_str, workflow_dir, project_root)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _resolve_path(path_str: str, workflow_dir: Path, project_root: Path) -> Path:
|
|
115
|
+
"""Resolve a path string relative to workflow dir or project root."""
|
|
116
|
+
if path_str.startswith("./"):
|
|
117
|
+
return workflow_dir / path_str[2:]
|
|
118
|
+
elif not Path(path_str).is_absolute():
|
|
119
|
+
return project_root / path_str
|
|
120
|
+
else:
|
|
121
|
+
return Path(path_str)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def count_steps(steps_path: Path) -> int:
|
|
125
|
+
"""Count step files in a directory."""
|
|
126
|
+
if not steps_path.is_dir():
|
|
127
|
+
return 0
|
|
128
|
+
return len([
|
|
129
|
+
f for f in steps_path.iterdir()
|
|
130
|
+
if f.is_file() and re.match(r"step-\d+", f.name) and f.suffix == ".md"
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def find_step_file(steps_path: Path, step_number: int) -> Path | None:
|
|
135
|
+
"""Find step file for a given step number.
|
|
136
|
+
|
|
137
|
+
Handles naming variants: step-01.md, step-01-name.md, step-1-name.md
|
|
138
|
+
"""
|
|
139
|
+
padded = f"{step_number:02d}"
|
|
140
|
+
matches = sorted([
|
|
141
|
+
f for f in steps_path.iterdir()
|
|
142
|
+
if f.is_file()
|
|
143
|
+
and (f.name.startswith(f"step-{padded}") or f.name.startswith(f"step-{step_number}-"))
|
|
144
|
+
and f.suffix == ".md"
|
|
145
|
+
])
|
|
146
|
+
return matches[0] if matches else None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def strip_frontmatter(content: str) -> str:
|
|
150
|
+
"""Remove YAML frontmatter from step file content."""
|
|
151
|
+
if not content.startswith("---"):
|
|
152
|
+
return content
|
|
153
|
+
end = content.find("---", 3)
|
|
154
|
+
if end == -1:
|
|
155
|
+
return content
|
|
156
|
+
return content[end + 3:].lstrip("\n")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def parse_session_field(content: str, field: str) -> str:
|
|
160
|
+
"""Extract a field value from session markdown.
|
|
161
|
+
|
|
162
|
+
Matches lines like: - **Field:** value
|
|
163
|
+
"""
|
|
164
|
+
pattern = rf"^- \*\*{re.escape(field)}:\*\*\s*(.+)$"
|
|
165
|
+
match = re.search(pattern, content, re.MULTILINE)
|
|
166
|
+
return match.group(1).strip() if match else ""
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def find_workflow_session(
|
|
170
|
+
session_dir: Path, workflow_name: str | None
|
|
171
|
+
) -> tuple[Path, str] | None:
|
|
172
|
+
"""Find workflow session file and determine workflow name.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
session_dir: Path to .session/ directory
|
|
176
|
+
workflow_name: Explicit workflow name, or None to auto-detect
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Tuple of (session_path, workflow_name), or None if not found
|
|
180
|
+
"""
|
|
181
|
+
if workflow_name:
|
|
182
|
+
session_file = session_dir / f"{workflow_name}-workflow-session.md"
|
|
183
|
+
if session_file.exists():
|
|
184
|
+
return session_file, workflow_name
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
# Auto-detect from session directory
|
|
188
|
+
sessions = sorted(session_dir.glob("*-workflow-session.md"))
|
|
189
|
+
if not sessions:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
session_file = sessions[0]
|
|
193
|
+
content = session_file.read_text()
|
|
194
|
+
|
|
195
|
+
# Try to extract workflow name from content
|
|
196
|
+
wf_match = re.search(r"^\*\*Workflow:\*\*\s*(.+)$", content, re.MULTILINE)
|
|
197
|
+
if wf_match:
|
|
198
|
+
name = wf_match.group(1).strip()
|
|
199
|
+
else:
|
|
200
|
+
name = session_file.stem.replace("-workflow-session", "")
|
|
201
|
+
|
|
202
|
+
return session_file, name
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def parse_steps_completed(value: str) -> list[int]:
|
|
206
|
+
"""Parse steps completed array from string like '[1, 2, 3]'."""
|
|
207
|
+
if not value or value == "[]":
|
|
208
|
+
return []
|
|
209
|
+
return [int(n) for n in re.findall(r"\d+", value)]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def format_steps_completed(steps: list[int]) -> str:
|
|
213
|
+
"""Format steps list as bracket notation."""
|
|
214
|
+
if not steps:
|
|
215
|
+
return "[]"
|
|
216
|
+
return "[" + ", ".join(str(s) for s in steps) + "]"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def find_story_session(session_dir: Path, story_id: str) -> Path | None:
|
|
220
|
+
"""Find a story session file by story ID.
|
|
221
|
+
|
|
222
|
+
Handles various naming patterns: 56-1-session.md, MSSCI-12190-session.md
|
|
223
|
+
Also searches file content for matching Jira/ID fields.
|
|
224
|
+
"""
|
|
225
|
+
# Try direct filename match
|
|
226
|
+
story_id_lower = story_id.lower()
|
|
227
|
+
for pattern in [f"{story_id}-session.md", f"{story_id_lower}-session.md"]:
|
|
228
|
+
candidate = session_dir / pattern
|
|
229
|
+
if candidate.exists():
|
|
230
|
+
return candidate
|
|
231
|
+
|
|
232
|
+
# Search file contents for matching story ID
|
|
233
|
+
for session_file in session_dir.glob("*-session.md"):
|
|
234
|
+
try:
|
|
235
|
+
content = session_file.read_text()
|
|
236
|
+
if f"Jira:** {story_id}" in content or f"ID:** {story_id}" in content:
|
|
237
|
+
return session_file
|
|
238
|
+
except OSError:
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
return None
|
|
@@ -181,107 +181,3 @@ def get_scale_level_info(level: int) -> dict[str, Any]:
|
|
|
181
181
|
return {"level": level, "scope": "unknown", "stories_min": 0,
|
|
182
182
|
"stories_max": 0, "workflow": "prd", "artifacts": []}
|
|
183
183
|
return SCALE_LEVELS[level].copy()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
# Phase ownership mapping for TDD workflow
|
|
187
|
-
# Canonical YAML names: setup, red, green, review, finish
|
|
188
|
-
TDD_PHASE_OWNERS: dict[str, str] = {
|
|
189
|
-
"setup": "sm",
|
|
190
|
-
"red": "tea",
|
|
191
|
-
"green": "dev",
|
|
192
|
-
"review": "reviewer",
|
|
193
|
-
"finish": "sm",
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
# Phase ownership mapping for trivial workflow (no TEA)
|
|
197
|
-
# Canonical YAML names: setup, implement, review, finish
|
|
198
|
-
TRIVIAL_PHASE_OWNERS: dict[str, str] = {
|
|
199
|
-
"setup": "sm",
|
|
200
|
-
"implement": "dev",
|
|
201
|
-
"review": "reviewer",
|
|
202
|
-
"finish": "sm",
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# All workflow phase mappings
|
|
206
|
-
WORKFLOW_PHASES: dict[str, dict[str, str]] = {
|
|
207
|
-
"tdd": TDD_PHASE_OWNERS,
|
|
208
|
-
"trivial": TRIVIAL_PHASE_OWNERS,
|
|
209
|
-
"bdd": TDD_PHASE_OWNERS, # BDD uses same phases as TDD
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def get_phase_owner(workflow: str, phase: str) -> str:
|
|
214
|
-
"""Get the agent that owns a workflow phase.
|
|
215
|
-
|
|
216
|
-
Args:
|
|
217
|
-
workflow: Workflow name (tdd, trivial, bdd)
|
|
218
|
-
phase: Phase name (setup, red, implement, review, approved)
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
Agent name (sm, tea, dev, reviewer)
|
|
222
|
-
"""
|
|
223
|
-
phases = WORKFLOW_PHASES.get(workflow, TDD_PHASE_OWNERS)
|
|
224
|
-
return phases.get(phase, "sm")
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def get_workflow_state() -> dict[str, Any]:
|
|
228
|
-
"""Get current workflow state from session files.
|
|
229
|
-
|
|
230
|
-
Scans .session/ directory for active session files and extracts
|
|
231
|
-
workflow state information.
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
Dict with state, story_id, workflow, phase fields
|
|
235
|
-
"""
|
|
236
|
-
from pathlib import Path
|
|
237
|
-
|
|
238
|
-
# Look for session files in .session/
|
|
239
|
-
session_dir = Path(".session")
|
|
240
|
-
if not session_dir.exists():
|
|
241
|
-
return {"state": "EMPTY_BACKLOG_STATE"}
|
|
242
|
-
|
|
243
|
-
# Find session files (pattern: *-session.md)
|
|
244
|
-
session_files = list(session_dir.glob("*-session.md"))
|
|
245
|
-
|
|
246
|
-
# Filter out workflow session files and archived files
|
|
247
|
-
story_sessions = [
|
|
248
|
-
f for f in session_files
|
|
249
|
-
if not f.name.startswith("prd-")
|
|
250
|
-
and not f.name.startswith("architecture-")
|
|
251
|
-
and not f.name.startswith("research-")
|
|
252
|
-
and "workflow" not in f.name.lower()
|
|
253
|
-
]
|
|
254
|
-
|
|
255
|
-
if not story_sessions:
|
|
256
|
-
return {"state": "NEW_WORK_STATE"}
|
|
257
|
-
|
|
258
|
-
# Read the most recent session file
|
|
259
|
-
session_file = max(story_sessions, key=lambda f: f.stat().st_mtime)
|
|
260
|
-
content = session_file.read_text()
|
|
261
|
-
|
|
262
|
-
# Extract fields from markdown format
|
|
263
|
-
# Session files use list format: "- **Field:** value"
|
|
264
|
-
# Also handle direct format: "**Field:** value"
|
|
265
|
-
result: dict[str, Any] = {"state": "IN_PROGRESS_STATE"}
|
|
266
|
-
|
|
267
|
-
for line in content.split("\n"):
|
|
268
|
-
# Strip leading "- " for list items
|
|
269
|
-
stripped = line.lstrip("- ").strip()
|
|
270
|
-
|
|
271
|
-
if stripped.startswith("**Story:**"):
|
|
272
|
-
result["story_id"] = stripped.replace("**Story:**", "").strip()
|
|
273
|
-
elif stripped.startswith("**Jira:**"):
|
|
274
|
-
result["story_id"] = stripped.replace("**Jira:**", "").strip()
|
|
275
|
-
elif stripped.startswith("**ID:**"):
|
|
276
|
-
# Also check **ID:** field (used in Story Details section)
|
|
277
|
-
if "story_id" not in result:
|
|
278
|
-
result["story_id"] = stripped.replace("**ID:**", "").strip()
|
|
279
|
-
elif stripped.startswith("**Type:**"):
|
|
280
|
-
# Workflow section uses **Type:** not **Workflow:**
|
|
281
|
-
result["workflow"] = stripped.replace("**Type:**", "").strip()
|
|
282
|
-
elif stripped.startswith("**Workflow:**"):
|
|
283
|
-
result["workflow"] = stripped.replace("**Workflow:**", "").strip()
|
|
284
|
-
elif stripped.startswith("**Phase:**"):
|
|
285
|
-
result["phase"] = stripped.replace("**Phase:**", "").strip()
|
|
286
|
-
|
|
287
|
-
return result
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow phase ownership and state detection.
|
|
3
|
+
|
|
4
|
+
Maps workflow phases to their owning agents and provides session
|
|
5
|
+
state detection from .session/ files.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Phase ownership mapping for TDD workflow
|
|
12
|
+
# Canonical YAML names: setup, red, green, review, finish
|
|
13
|
+
TDD_PHASE_OWNERS: dict[str, str] = {
|
|
14
|
+
"setup": "sm",
|
|
15
|
+
"red": "tea",
|
|
16
|
+
"green": "dev",
|
|
17
|
+
"review": "reviewer",
|
|
18
|
+
"finish": "sm",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Phase ownership mapping for trivial workflow (no TEA)
|
|
22
|
+
# Canonical YAML names: setup, implement, review, finish
|
|
23
|
+
TRIVIAL_PHASE_OWNERS: dict[str, str] = {
|
|
24
|
+
"setup": "sm",
|
|
25
|
+
"implement": "dev",
|
|
26
|
+
"review": "reviewer",
|
|
27
|
+
"finish": "sm",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# All workflow phase mappings
|
|
31
|
+
WORKFLOW_PHASES: dict[str, dict[str, str]] = {
|
|
32
|
+
"tdd": TDD_PHASE_OWNERS,
|
|
33
|
+
"trivial": TRIVIAL_PHASE_OWNERS,
|
|
34
|
+
"bdd": TDD_PHASE_OWNERS, # BDD uses same phases as TDD
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_phase_owner(workflow: str, phase: str) -> str:
|
|
39
|
+
"""Get the agent that owns a workflow phase.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
workflow: Workflow name (tdd, trivial, bdd)
|
|
43
|
+
phase: Phase name (setup, red, implement, review, approved)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Agent name (sm, tea, dev, reviewer)
|
|
47
|
+
"""
|
|
48
|
+
phases = WORKFLOW_PHASES.get(workflow, TDD_PHASE_OWNERS)
|
|
49
|
+
return phases.get(phase, "sm")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_workflow_state() -> dict[str, Any]:
|
|
53
|
+
"""Get current workflow state from session files.
|
|
54
|
+
|
|
55
|
+
Scans .session/ directory for active session files and extracts
|
|
56
|
+
workflow state information.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dict with state, story_id, workflow, phase fields
|
|
60
|
+
"""
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
|
|
63
|
+
# Look for session files in .session/
|
|
64
|
+
session_dir = Path(".session")
|
|
65
|
+
if not session_dir.exists():
|
|
66
|
+
return {"state": "EMPTY_BACKLOG_STATE"}
|
|
67
|
+
|
|
68
|
+
# Find session files (pattern: *-session.md)
|
|
69
|
+
session_files = list(session_dir.glob("*-session.md"))
|
|
70
|
+
|
|
71
|
+
# Filter out workflow session files and archived files
|
|
72
|
+
story_sessions = [
|
|
73
|
+
f for f in session_files
|
|
74
|
+
if not f.name.startswith("prd-")
|
|
75
|
+
and not f.name.startswith("architecture-")
|
|
76
|
+
and not f.name.startswith("research-")
|
|
77
|
+
and "workflow" not in f.name.lower()
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
if not story_sessions:
|
|
81
|
+
return {"state": "NEW_WORK_STATE"}
|
|
82
|
+
|
|
83
|
+
# Read the most recent session file
|
|
84
|
+
session_file = max(story_sessions, key=lambda f: f.stat().st_mtime)
|
|
85
|
+
content = session_file.read_text()
|
|
86
|
+
|
|
87
|
+
# Extract fields from markdown format
|
|
88
|
+
# Session files use list format: "- **Field:** value"
|
|
89
|
+
# Also handle direct format: "**Field:** value"
|
|
90
|
+
result: dict[str, Any] = {"state": "IN_PROGRESS_STATE"}
|
|
91
|
+
|
|
92
|
+
for line in content.split("\n"):
|
|
93
|
+
# Strip leading "- " for list items
|
|
94
|
+
stripped = line.lstrip("- ").strip()
|
|
95
|
+
|
|
96
|
+
if stripped.startswith("**Story:**"):
|
|
97
|
+
result["story_id"] = stripped.replace("**Story:**", "").strip()
|
|
98
|
+
elif stripped.startswith("**Jira:**"):
|
|
99
|
+
result["story_id"] = stripped.replace("**Jira:**", "").strip()
|
|
100
|
+
elif stripped.startswith("**ID:**"):
|
|
101
|
+
# Also check **ID:** field (used in Story Details section)
|
|
102
|
+
if "story_id" not in result:
|
|
103
|
+
result["story_id"] = stripped.replace("**ID:**", "").strip()
|
|
104
|
+
elif stripped.startswith("**Type:**"):
|
|
105
|
+
# Workflow section uses **Type:** not **Workflow:**
|
|
106
|
+
result["workflow"] = stripped.replace("**Type:**", "").strip()
|
|
107
|
+
elif stripped.startswith("**Workflow:**"):
|
|
108
|
+
result["workflow"] = stripped.replace("**Workflow:**", "").strip()
|
|
109
|
+
elif stripped.startswith("**Phase:**"):
|
|
110
|
+
result["phase"] = stripped.replace("**Phase:**", "").strip()
|
|
111
|
+
|
|
112
|
+
return result
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Meta Scripts
|
|
2
|
+
|
|
3
|
+
**These scripts are NOT distributed to users.** They are for Pennyfarthing framework development only.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
| Script | Purpose |
|
|
8
|
+
|--------|---------|
|
|
9
|
+
| `deploy.sh` | Release Pennyfarthing (version bump, tag, push, GitHub release) |
|
|
10
|
+
| `cyclist-debug.mjs` | Debug Cyclist connection |
|
|
11
|
+
| `handoff-cli.{sh,js}` | Test handoff flow |
|
|
12
|
+
| `verify-visual-mapping.js` | Verify theme visual mappings |
|
|
13
|
+
| `migrate-assets-to-slug.sh` | One-time migration script |
|
|
14
|
+
| `resize-portraits.sh` | Resize portrait images |
|
|
15
|
+
| `resolve-portrait.mjs` | Portrait resolution logic |
|
|
16
|
+
| `validate-refs.js` | Validate internal references |
|
|
17
|
+
|
|
18
|
+
> Benchmark scripts have been moved to `packages/benchmark/`.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Run from pennyfarthing repo root:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Release a new version
|
|
26
|
+
./scripts/deploy.sh --dry-run patch
|
|
27
|
+
./scripts/deploy.sh patch
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Where Should My Script Go?
|
|
31
|
+
|
|
32
|
+
**Put it here if:**
|
|
33
|
+
- It's for framework development/CI only
|
|
34
|
+
- Users should NOT have access to it
|
|
35
|
+
- It uses GPU/heavy dependencies (keep in meta, not distributed)
|
|
36
|
+
|
|
37
|
+
**Put it in `pennyfarthing-dist/scripts/` if:**
|
|
38
|
+
- Users need it for their workflows
|
|
39
|
+
- It's part of the sprint/story/jira tooling
|
|
40
|
+
|
|
41
|
+
See `CLAUDE.md` for the full decision tree.
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# List all available workflows with type indicators
|
|
3
|
-
# Usage: .pennyfarthing/scripts/workflow/list-workflows.sh
|
|
4
|
-
# or: Invoked with PROJECT_ROOT already set
|
|
5
|
-
#
|
|
6
|
-
# MSSCI-12083: Added type, steps, and modes columns
|
|
7
|
-
|
|
8
|
-
set -euo pipefail
|
|
9
|
-
|
|
10
|
-
# PROJECT_ROOT should be set by find-root.sh, but find it if not
|
|
11
|
-
if [[ -z "${PROJECT_ROOT:-}" ]]; then
|
|
12
|
-
d="$PWD"
|
|
13
|
-
while [[ ! -d "$d/.claude" ]] && [[ "$d" != "/" ]]; do
|
|
14
|
-
d="$(dirname "$d")"
|
|
15
|
-
done
|
|
16
|
-
PROJECT_ROOT="$d"
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
WORKFLOWS_DIR="$PROJECT_ROOT/.pennyfarthing/workflows"
|
|
20
|
-
|
|
21
|
-
if [[ ! -d "$WORKFLOWS_DIR" ]]; then
|
|
22
|
-
echo "Error: Workflows directory not found at $WORKFLOWS_DIR"
|
|
23
|
-
exit 1
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
if ! command -v yq &> /dev/null; then
|
|
27
|
-
echo "Error: yq is required but not installed"
|
|
28
|
-
echo "Install with: brew install yq"
|
|
29
|
-
exit 1
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
echo "# Available Workflows"
|
|
33
|
-
echo ""
|
|
34
|
-
echo "| Workflow | Type | Default | Steps/Phases | Modes | Description |"
|
|
35
|
-
echo "|----------|------|---------|--------------|-------|-------------|"
|
|
36
|
-
|
|
37
|
-
for f in "$WORKFLOWS_DIR"/*.yaml; do
|
|
38
|
-
[[ -f "$f" ]] || continue
|
|
39
|
-
|
|
40
|
-
name=$(yq eval '.workflow.name' "$f")
|
|
41
|
-
desc=$(yq eval '.workflow.description' "$f" | head -1)
|
|
42
|
-
is_default=$(yq eval '.workflow.triggers.default // false' "$f")
|
|
43
|
-
|
|
44
|
-
# Detect workflow type (stepped vs phased)
|
|
45
|
-
# Stepped workflows have .workflow.type == "stepped" or .workflow.steps
|
|
46
|
-
workflow_type=$(yq eval '.workflow.type // "phased"' "$f")
|
|
47
|
-
has_steps=$(yq eval '.workflow.steps != null' "$f")
|
|
48
|
-
|
|
49
|
-
if [[ "$has_steps" == "true" ]] || [[ "$workflow_type" == "stepped" ]]; then
|
|
50
|
-
type_col="stepped"
|
|
51
|
-
# Count step files if steps.path is defined
|
|
52
|
-
steps_path=$(yq eval '.workflow.steps.path // ""' "$f")
|
|
53
|
-
steps_pattern=$(yq eval '.workflow.steps.pattern // "step-*.md"' "$f")
|
|
54
|
-
if [[ -n "$steps_path" ]] && [[ -d "$PROJECT_ROOT/$steps_path" ]]; then
|
|
55
|
-
step_count=$(find "$PROJECT_ROOT/$steps_path" -name "$steps_pattern" 2>/dev/null | wc -l | tr -d ' ')
|
|
56
|
-
steps_col="${step_count} steps"
|
|
57
|
-
else
|
|
58
|
-
steps_col="-"
|
|
59
|
-
fi
|
|
60
|
-
else
|
|
61
|
-
type_col="phased"
|
|
62
|
-
# Count phases for phased workflows
|
|
63
|
-
phase_count=$(yq eval '.workflow.phases | length' "$f")
|
|
64
|
-
steps_col="${phase_count} phases"
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# Default column
|
|
68
|
-
if [[ "$is_default" == "true" ]]; then
|
|
69
|
-
default_col="yes"
|
|
70
|
-
else
|
|
71
|
-
default_col="no"
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
# Modes column (for tri-modal workflows)
|
|
75
|
-
modes=$(yq eval '.workflow.modes.available // []' "$f")
|
|
76
|
-
if [[ "$modes" != "[]" ]] && [[ "$modes" != "null" ]]; then
|
|
77
|
-
# Format as comma-separated list
|
|
78
|
-
modes_col=$(yq eval '.workflow.modes.available | join(",")' "$f")
|
|
79
|
-
else
|
|
80
|
-
modes_col="-"
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
echo "| $name | $type_col | $default_col | $steps_col | $modes_col | $desc |"
|
|
84
|
-
done
|
|
85
|
-
|
|
86
|
-
echo ""
|
|
87
|
-
echo "**Legend:**"
|
|
88
|
-
echo "- **phased**: Agent-driven workflow (SM → TEA → Dev → Reviewer)"
|
|
89
|
-
echo "- **stepped**: Step-by-step guided workflow with progressive disclosure"
|
|
90
|
-
echo ""
|
|
91
|
-
echo "Use \`/workflow show <name>\` for workflow details."
|