@pennyfarthing/core 11.1.1 → 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 +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/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/check-context.sh +1 -1
- 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/worktree-manager.sh +5 -496
- 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/otel-auto-config.sh +9 -11
- package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +1 -1
- package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +76 -0
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +4 -221
- 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-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/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.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/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/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/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
|
@@ -11,25 +11,22 @@ Pennyfarthing uses YAML-defined workflows to control agent sequences. The defaul
|
|
|
11
11
|
|
|
12
12
|
## Quick Reference
|
|
13
13
|
|
|
14
|
-
### Shell Script Commands
|
|
15
|
-
|
|
16
|
-
| Command | Script | Purpose |
|
|
17
|
-
|---------|--------|---------|
|
|
18
|
-
| `/pf-workflow` or `/pf-workflow list` | `.pennyfarthing/scripts/workflow/list-workflows.sh` | List all workflows |
|
|
19
|
-
| `/pf-workflow show [name]` | `.pennyfarthing/scripts/workflow/show-workflow.sh [name]` | Show workflow details |
|
|
20
|
-
| `/pf-workflow set <name>` | Edit session file `**Workflow:**` line | Switch workflow mid-session |
|
|
21
|
-
| `/pf-workflow start <name>` | `.pennyfarthing/scripts/workflow/start-workflow.sh <name> [--mode M]` | Start stepped workflow |
|
|
22
|
-
| `/pf-workflow resume [name]` | `.pennyfarthing/scripts/workflow/resume-workflow.sh [name]` | Resume interrupted workflow |
|
|
23
|
-
| `/pf-workflow status` | `.pennyfarthing/scripts/workflow/workflow-status.sh` | Show stepped workflow progress |
|
|
24
|
-
| `/pf-workflow fix-phase <id> <phase>` | `.pennyfarthing/scripts/workflow/fix-session-phase.sh <id> <phase> [--dry-run]` | Repair session phase |
|
|
25
|
-
|
|
26
14
|
### Python CLI Commands
|
|
27
15
|
|
|
28
16
|
| Command | CLI | Purpose |
|
|
29
17
|
|---------|-----|---------|
|
|
18
|
+
| `/pf-workflow` or `/pf-workflow list` | `pf workflow list` | List all workflows |
|
|
19
|
+
| `/pf-workflow show [name]` | `pf workflow show [name]` | Show workflow details |
|
|
20
|
+
| `/pf-workflow set <name>` | Edit session file `**Workflow:**` line | Switch workflow mid-session |
|
|
21
|
+
| `/pf-workflow start <name>` | `pf workflow start <name> [--mode M]` | Start stepped workflow |
|
|
22
|
+
| `/pf-workflow resume [name]` | `pf workflow resume [name]` | Resume interrupted workflow |
|
|
23
|
+
| `/pf-workflow status` | `pf workflow status` | Show stepped workflow progress |
|
|
24
|
+
| `/pf-workflow fix-phase <id> <phase>` | `pf workflow fix-phase <id> <phase> [--dry-run]` | Repair session phase |
|
|
30
25
|
| Check workflow state | `pf workflow check [--json]` | Current story, phase, state |
|
|
31
26
|
| Check phase owner | `pf workflow phase-check <workflow> <phase>` | Which agent owns a phase |
|
|
27
|
+
| Get workflow type | `pf workflow type <workflow>` | phased/stepped/procedural |
|
|
32
28
|
| Emit handoff marker | `pf workflow handoff <next-agent>` | CYCLIST handoff marker |
|
|
29
|
+
| Complete step | `pf workflow complete-step [name] [--step N]` | Advance stepped workflow |
|
|
33
30
|
|
|
34
31
|
### Built-in Workflows
|
|
35
32
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Workflow — Detailed Usage
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Commands
|
|
4
4
|
|
|
5
5
|
### List Workflows
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
pf workflow list
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
No arguments. Returns table of workflows with type, phases/steps count, modes, default flag, description.
|
|
@@ -13,7 +13,7 @@ No arguments. Returns table of workflows with type, phases/steps count, modes, d
|
|
|
13
13
|
### Show Workflow
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
pf workflow show [NAME]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
| Arg | Required | Description |
|
|
@@ -31,7 +31,7 @@ Warning: Only do this when story requirements have fundamentally changed. Switch
|
|
|
31
31
|
### Start Stepped Workflow
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
|
|
34
|
+
pf workflow start <NAME> [--mode <MODE>]
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
| Arg/Option | Required | Description |
|
|
@@ -44,7 +44,7 @@ Creates a new session and begins at step 1.
|
|
|
44
44
|
### Resume Stepped Workflow
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
pf workflow resume [NAME]
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
| Arg | Required | Description |
|
|
@@ -54,7 +54,7 @@ Creates a new session and begins at step 1.
|
|
|
54
54
|
### Workflow Status
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
|
|
57
|
+
pf workflow status
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
No arguments. Shows current step, progress percentage, mode.
|
|
@@ -62,7 +62,7 @@ No arguments. Shows current step, progress percentage, mode.
|
|
|
62
62
|
### Fix Session Phase
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
|
|
65
|
+
pf workflow fix-phase <STORY_ID> <TARGET_PHASE> [--dry-run]
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
| Arg/Option | Required | Description |
|
|
@@ -80,9 +80,22 @@ Valid phases by workflow:
|
|
|
80
80
|
|
|
81
81
|
Updates `**Phase:**` field, `**Phase Started:**` timestamp, and handoff history table.
|
|
82
82
|
|
|
83
|
+
### Complete Step
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pf workflow complete-step [NAME] [--step N]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Arg/Option | Required | Description |
|
|
90
|
+
|------------|----------|-------------|
|
|
91
|
+
| `NAME` | No | Workflow name (auto-detects from session) |
|
|
92
|
+
| `--step N` | No | Complete specific step number instead of current |
|
|
93
|
+
|
|
94
|
+
Advances session: increments step, updates completed list, recalculates percentage.
|
|
95
|
+
|
|
83
96
|
---
|
|
84
97
|
|
|
85
|
-
##
|
|
98
|
+
## State & Phase Commands
|
|
86
99
|
|
|
87
100
|
### Check Workflow State
|
|
88
101
|
|
|
@@ -109,6 +122,18 @@ pf workflow phase-check <WORKFLOW_NAME> <PHASE>
|
|
|
109
122
|
|
|
110
123
|
Returns the agent name that owns the phase.
|
|
111
124
|
|
|
125
|
+
### Get Workflow Type
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pf workflow type <WORKFLOW_NAME>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Arg | Required | Description |
|
|
132
|
+
|-----|----------|-------------|
|
|
133
|
+
| `WORKFLOW_NAME` | Yes | Workflow name (e.g., `tdd`, `architecture`) |
|
|
134
|
+
|
|
135
|
+
Returns `phased`, `stepped`, or `procedural`.
|
|
136
|
+
|
|
112
137
|
### Emit Handoff Marker
|
|
113
138
|
|
|
114
139
|
```bash
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
# BDD Tandem Workflow - BDD with Full Tandem Chain
|
|
2
|
-
# Extends standard BDD with tandem observers:
|
|
3
|
-
# - Architect backseats UX-Designer during design
|
|
4
|
-
# - UX-Designer backseats Dev during implementation
|
|
5
|
-
# - PM backseats Reviewer during review
|
|
6
2
|
#
|
|
7
|
-
#
|
|
3
|
+
# When to use: Best for UI/UX-heavy stories where designer-architect pairing
|
|
4
|
+
# shapes implementation. Use this instead of base bdd when the story involves
|
|
5
|
+
# complex user flows, new component patterns, or cross-domain UI integration.
|
|
6
|
+
#
|
|
7
|
+
# Tandem pairings:
|
|
8
|
+
# - DESIGN phase: UX-Designer + Architect (file-watch) — Architect validates technical feasibility
|
|
9
|
+
# - RED phase: TEA + Dev (file-watch) — Dev provides implementation context for test strategy
|
|
10
|
+
# - GREEN phase: Dev + UX-Designer (file-watch) — UX-Designer ensures design fidelity
|
|
11
|
+
# - REVIEW phase: Reviewer + PM (file-watch) — PM validates business requirements
|
|
12
|
+
#
|
|
13
|
+
# Compared to base bdd: Adds tandem observers on every working phase.
|
|
14
|
+
# Designer-developer pairing ensures UX intent survives implementation.
|
|
15
|
+
#
|
|
16
|
+
# Flow: SM → UX-Designer (+Architect) → TEA (+Dev) → Dev (+UX-Designer) → Reviewer (+PM) → SM
|
|
8
17
|
|
|
9
18
|
workflow:
|
|
10
19
|
name: bdd-tandem
|
|
11
|
-
description: BDD with full tandem chain
|
|
20
|
+
description: BDD with full tandem chain — designer and architect consultation across phases
|
|
12
21
|
version: "1.0.0"
|
|
13
22
|
|
|
14
23
|
phases:
|
|
@@ -36,6 +45,9 @@ workflow:
|
|
|
36
45
|
file: gates/tests-fail
|
|
37
46
|
type: tests_fail
|
|
38
47
|
condition: Behavior scenarios have test coverage
|
|
48
|
+
tandem:
|
|
49
|
+
partner: dev
|
|
50
|
+
scope: file-watch
|
|
39
51
|
|
|
40
52
|
- name: green
|
|
41
53
|
agent: dev
|
|
@@ -32,7 +32,7 @@ Build a complete picture of uncommitted changes across ALL repos defined in `.cl
|
|
|
32
32
|
**CRITICAL: Use the multi-repo script, not plain `git status`.**
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
|
|
35
|
+
pf git status
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
This shows branch, staged/unstaged changes, and unpushed commits for **all repos** defined in the project configuration.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Review Tandem Workflow - TDD with Focused Architectural Review
|
|
2
|
+
#
|
|
3
|
+
# When to use: Best for stories that primarily need architectural review during
|
|
4
|
+
# the review phase. Use this for large refactors (10+ pts), cross-cutting
|
|
5
|
+
# infrastructure changes, or stories where an architect audit catches issues
|
|
6
|
+
# that a standard code review might miss.
|
|
7
|
+
#
|
|
8
|
+
# Tandem pairings:
|
|
9
|
+
# - REVIEW phase: Reviewer + Architect (file-watch) — Architect provides
|
|
10
|
+
# focused architectural audit alongside standard code review
|
|
11
|
+
#
|
|
12
|
+
# Compared to base tdd: Same phase flow, but adds architect consultation
|
|
13
|
+
# during review only. Lighter than tdd-tandem (no tandem on red/green phases).
|
|
14
|
+
#
|
|
15
|
+
# Flow: SM → TEA → Dev → Reviewer (+Architect) → SM
|
|
16
|
+
|
|
17
|
+
workflow:
|
|
18
|
+
name: review-tandem
|
|
19
|
+
description: TDD with tandem architect consultation on review phase
|
|
20
|
+
version: "1.0.0"
|
|
21
|
+
|
|
22
|
+
phases:
|
|
23
|
+
- name: setup
|
|
24
|
+
agent: sm
|
|
25
|
+
output: [session_file, branches, story_context]
|
|
26
|
+
|
|
27
|
+
- name: red
|
|
28
|
+
agent: tea
|
|
29
|
+
input: [session_file, story_context]
|
|
30
|
+
output: [failing_tests]
|
|
31
|
+
gate:
|
|
32
|
+
file: gates/tests-fail
|
|
33
|
+
type: tests_fail
|
|
34
|
+
condition: All acceptance criteria have test coverage
|
|
35
|
+
|
|
36
|
+
- name: green
|
|
37
|
+
agent: dev
|
|
38
|
+
input: [failing_tests, story_context]
|
|
39
|
+
output: [implementation, passing_tests]
|
|
40
|
+
gate:
|
|
41
|
+
file: gates/tests-pass
|
|
42
|
+
type: tests_pass
|
|
43
|
+
condition: All tests passing, no skipped tests
|
|
44
|
+
|
|
45
|
+
- name: review
|
|
46
|
+
agent: reviewer
|
|
47
|
+
input: [implementation, passing_tests]
|
|
48
|
+
output: [approval]
|
|
49
|
+
gate:
|
|
50
|
+
file: gates/approval
|
|
51
|
+
type: approval
|
|
52
|
+
condition: Code review approved, architecture validated
|
|
53
|
+
tandem:
|
|
54
|
+
partner: architect
|
|
55
|
+
scope: file-watch
|
|
56
|
+
|
|
57
|
+
- name: finish
|
|
58
|
+
agent: sm
|
|
59
|
+
input: [approval]
|
|
60
|
+
output: [archived_session, story_summary]
|
|
61
|
+
|
|
62
|
+
triggers:
|
|
63
|
+
tags: [review-tandem, tandem]
|
|
64
|
+
points:
|
|
65
|
+
min: 8
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
# TDD Tandem Workflow - TDD with Full Tandem Chain
|
|
2
|
-
# Extends standard TDD with tandem observers on every phase:
|
|
3
|
-
# - Architect backseats TEA during red phase
|
|
4
|
-
# - TEA backseats Dev during green phase
|
|
5
|
-
# - PM backseats Reviewer during review phase
|
|
6
2
|
#
|
|
7
|
-
#
|
|
3
|
+
# When to use: Best for 5+ point feature stories where architectural alignment
|
|
4
|
+
# during implementation provides high value. Use this instead of base tdd when
|
|
5
|
+
# the story involves cross-cutting concerns, new patterns, or complex integrations.
|
|
6
|
+
#
|
|
7
|
+
# Tandem pairings:
|
|
8
|
+
# - RED phase: TEA + Architect (file-watch) — Architect reviews test strategy
|
|
9
|
+
# - GREEN phase: Dev + Architect (file-watch) — Architect guides implementation patterns
|
|
10
|
+
# - REVIEW phase: Reviewer + PM (file-watch) — PM validates business requirements
|
|
11
|
+
#
|
|
12
|
+
# Compared to base tdd: Adds tandem observers on every working phase.
|
|
13
|
+
# The extra context from partner agents helps catch architectural drift early.
|
|
14
|
+
#
|
|
15
|
+
# Flow: SM → TEA (+Architect) → Dev (+Architect) → Reviewer (+PM) → SM
|
|
8
16
|
|
|
9
17
|
workflow:
|
|
10
18
|
name: tdd-tandem
|
|
11
|
-
description: TDD with full tandem chain across
|
|
19
|
+
description: TDD with full tandem chain — architect and PM consultation across phases
|
|
12
20
|
version: "2.0.0"
|
|
13
21
|
|
|
14
22
|
phases:
|
|
@@ -37,7 +45,7 @@ workflow:
|
|
|
37
45
|
type: tests_pass
|
|
38
46
|
condition: All tests passing, no skipped tests
|
|
39
47
|
tandem:
|
|
40
|
-
partner:
|
|
48
|
+
partner: architect
|
|
41
49
|
scope: file-watch
|
|
42
50
|
|
|
43
51
|
- name: review
|
|
@@ -60,5 +68,5 @@ workflow:
|
|
|
60
68
|
triggers:
|
|
61
69
|
tags: [tandem]
|
|
62
70
|
points:
|
|
63
|
-
min:
|
|
71
|
+
min: 5
|
|
64
72
|
default: false
|
|
@@ -12,8 +12,9 @@ Python Click CLI for Pennyfarthing. Entry point: `pennyfarthing_scripts/cli.py`.
|
|
|
12
12
|
| `pf bc` | Panel focus and layout management | `bc/cli.py` |
|
|
13
13
|
| `pf validate` | Project validators | `validate/cli.py` |
|
|
14
14
|
| `pf agent` | Agent session management | `cli.py` (inline) |
|
|
15
|
-
| `pf workflow` | Workflow state and phase management | `cli.py`
|
|
15
|
+
| `pf workflow` | Workflow state and phase management | `workflow/cli.py` |
|
|
16
16
|
| `pf bikerack` | BikeRack dashboard launcher | `bikerack/cli.py` |
|
|
17
|
+
| `pf git` | Repository operations (status, branches, worktree, hooks) | `git_group/cli.py` |
|
|
17
18
|
| `pf debug` | Analysis tools (hotspots, deadcode, healthscore) | `cli.py` (inline group) |
|
|
18
19
|
|
|
19
20
|
### Sugar Shortcuts
|
|
@@ -139,9 +140,17 @@ Options: `--session-id`, `--no-persona`, `--json`, `--minimal`, `--full`, `--qui
|
|
|
139
140
|
|
|
140
141
|
| Command | Description | Source |
|
|
141
142
|
|---------|-------------|--------|
|
|
142
|
-
| `pf workflow check` | Current workflow state | `workflow.py` |
|
|
143
|
-
| `pf workflow phase-check WORKFLOW PHASE` | Check phase owner | `workflow.py` |
|
|
144
|
-
| `pf workflow handoff AGENT` | Emit handoff marker | `cli.py`
|
|
143
|
+
| `pf workflow check [--json]` | Current workflow state | `workflow/cli.py` |
|
|
144
|
+
| `pf workflow phase-check WORKFLOW PHASE` | Check phase owner | `workflow/cli.py` |
|
|
145
|
+
| `pf workflow handoff AGENT` | Emit handoff marker | `workflow/cli.py` |
|
|
146
|
+
| `pf workflow type WORKFLOW` | Get workflow type (phased/stepped/procedural) | `workflow/cli.py` |
|
|
147
|
+
| `pf workflow list` | List all available workflows | `workflow/cli.py` |
|
|
148
|
+
| `pf workflow show [NAME]` | Show workflow details | `workflow/cli.py` |
|
|
149
|
+
| `pf workflow start NAME [--mode M]` | Start stepped workflow | `workflow/cli.py` |
|
|
150
|
+
| `pf workflow resume [NAME]` | Resume interrupted workflow | `workflow/cli.py` |
|
|
151
|
+
| `pf workflow status [NAME]` | Show stepped workflow progress | `workflow/cli.py` |
|
|
152
|
+
| `pf workflow fix-phase ID PHASE [--dry-run]` | Repair session phase | `workflow/cli.py` |
|
|
153
|
+
| `pf workflow complete-step [NAME] [--step N]` | Complete current step | `workflow/cli.py` |
|
|
145
154
|
|
|
146
155
|
## pf bikerack
|
|
147
156
|
|
|
@@ -151,6 +160,19 @@ Options: `--session-id`, `--no-persona`, `--json`, `--minimal`, `--full`, `--qui
|
|
|
151
160
|
| `pf bikerack stop` | Stop running instance | `bikerack/cli.py` |
|
|
152
161
|
| `pf bikerack status` | Show running state | `bikerack/cli.py` |
|
|
153
162
|
|
|
163
|
+
## pf git
|
|
164
|
+
|
|
165
|
+
| Command | Description | Source |
|
|
166
|
+
|---------|-------------|--------|
|
|
167
|
+
| `pf git status [--brief]` | Check git status of all repos | `git/status_all.py` |
|
|
168
|
+
| `pf git branches BRANCH [--repos all\|api\|ui]` | Create feature branches | `git/create_branches.py` |
|
|
169
|
+
| `pf git cleanup` | Start git-cleanup workflow | `git_group/cli.py` |
|
|
170
|
+
| `pf git worktree create NAME BRANCH` | Create worktree(s) | `git/worktree.py` |
|
|
171
|
+
| `pf git worktree remove NAME` | Remove worktree | `git/worktree.py` |
|
|
172
|
+
| `pf git worktree list` | List active worktrees | `git/worktree.py` |
|
|
173
|
+
| `pf git worktree status` | Show worktree status | `git/worktree.py` |
|
|
174
|
+
| `pf git install-hooks` | Install git hooks with .d/ dispatcher | `git/hooks_installer.py` |
|
|
175
|
+
|
|
154
176
|
## pf debug
|
|
155
177
|
|
|
156
178
|
| Command | Description | Source |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -34,8 +34,8 @@ from pennyfarthing_scripts.bc.focus import (
|
|
|
34
34
|
def _get_current_layout() -> dict | None:
|
|
35
35
|
"""Fetch the current layout from a running Cyclist or BikeRack server.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
Reads .wheelhub-port (shared by both Cyclist and BikeRack) and fetches
|
|
38
|
+
the layout endpoint.
|
|
39
39
|
|
|
40
40
|
Returns:
|
|
41
41
|
Layout dict, or None if no server is running or fetch fails.
|
|
@@ -44,10 +44,8 @@ def _get_current_layout() -> dict | None:
|
|
|
44
44
|
|
|
45
45
|
root = _get_root()
|
|
46
46
|
|
|
47
|
-
# Try BikeRack first (bikerack-layout), then Cyclist (layout)
|
|
48
47
|
candidates = [
|
|
49
|
-
(root / ".
|
|
50
|
-
(root / ".cyclist-port", "/api/settings/bikerack-layout"),
|
|
48
|
+
(root / ".wheelhub-port", "/api/settings/bikerack-layout"),
|
|
51
49
|
]
|
|
52
50
|
|
|
53
51
|
for port_file, endpoint in candidates:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,12 +6,13 @@ list with status indicators (running, completed, failed).
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import time
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from rich.console import Group
|
|
12
13
|
from rich.text import Text
|
|
13
14
|
|
|
14
|
-
from pennyfarthing_scripts.bikerack.base_panel import PANEL_ICONS, BasePanel
|
|
15
|
+
from pennyfarthing_scripts.bikerack.base_panel import PANEL_ICONS, BasePanel, format_duration
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class BackgroundPanel(BasePanel):
|
|
@@ -25,12 +26,62 @@ class BackgroundPanel(BasePanel):
|
|
|
25
26
|
panel_name: str = "Background"
|
|
26
27
|
icon: str = PANEL_ICONS["background"][0]
|
|
27
28
|
|
|
29
|
+
def __init__(self, client=None, **kwargs):
|
|
30
|
+
super().__init__(client=client, **kwargs)
|
|
31
|
+
self._timer = None
|
|
32
|
+
|
|
33
|
+
def on_mount(self) -> None:
|
|
34
|
+
"""Subscribe to channel and start elapsed timer."""
|
|
35
|
+
super().on_mount()
|
|
36
|
+
self._timer = self.set_interval(1, self._tick)
|
|
37
|
+
|
|
38
|
+
def on_unmount(self) -> None:
|
|
39
|
+
"""Stop timer and cleanup."""
|
|
40
|
+
if self._timer is not None:
|
|
41
|
+
self._timer.stop()
|
|
42
|
+
super().on_unmount()
|
|
43
|
+
|
|
44
|
+
def _tick(self) -> None:
|
|
45
|
+
"""Re-render every second to update elapsed times."""
|
|
46
|
+
if self._last_payload is not None:
|
|
47
|
+
rendered = self.render_panel(self._last_payload)
|
|
48
|
+
try:
|
|
49
|
+
self.update(rendered)
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
28
53
|
def render_panel(self, payload: dict[str, Any]) -> Any:
|
|
29
54
|
"""Render background task data from WebSocket payload."""
|
|
30
55
|
tasks = payload.get("tasks", [])
|
|
31
56
|
if not isinstance(tasks, list) or not tasks:
|
|
32
57
|
return Text("No background tasks", style="dim italic")
|
|
33
58
|
|
|
59
|
+
running = 0
|
|
60
|
+
done = 0
|
|
61
|
+
failed = 0
|
|
62
|
+
for task in tasks:
|
|
63
|
+
if not isinstance(task, dict):
|
|
64
|
+
continue
|
|
65
|
+
if task.get("completedAt") is not None:
|
|
66
|
+
if task.get("success"):
|
|
67
|
+
done += 1
|
|
68
|
+
else:
|
|
69
|
+
failed += 1
|
|
70
|
+
else:
|
|
71
|
+
running += 1
|
|
72
|
+
|
|
73
|
+
# Summary header
|
|
74
|
+
summary = Text()
|
|
75
|
+
summary.append(f"{running} running", style="yellow")
|
|
76
|
+
summary.append(" ")
|
|
77
|
+
summary.append(f"{done} done", style="green")
|
|
78
|
+
if failed > 0:
|
|
79
|
+
summary.append(" ")
|
|
80
|
+
summary.append(f"{failed} failed", style="red")
|
|
81
|
+
|
|
82
|
+
separator = Text("─" * 30, style="dim")
|
|
83
|
+
|
|
84
|
+
# Task list
|
|
34
85
|
parts: list[Any] = []
|
|
35
86
|
for task in tasks:
|
|
36
87
|
if not isinstance(task, dict):
|
|
@@ -40,7 +91,7 @@ class BackgroundPanel(BasePanel):
|
|
|
40
91
|
if not parts:
|
|
41
92
|
return Text("No background tasks", style="dim italic")
|
|
42
93
|
|
|
43
|
-
return Group(*parts)
|
|
94
|
+
return Group(summary, separator, *parts)
|
|
44
95
|
|
|
45
96
|
|
|
46
97
|
def _render_task(task: dict[str, Any]) -> Text:
|
|
@@ -48,6 +99,7 @@ def _render_task(task: dict[str, Any]) -> Text:
|
|
|
48
99
|
description = task.get("description", "Unknown task")
|
|
49
100
|
subagent_type = task.get("subagentType", "")
|
|
50
101
|
completed_at = task.get("completedAt")
|
|
102
|
+
started_at = task.get("startedAt")
|
|
51
103
|
success = task.get("success")
|
|
52
104
|
result = task.get("result", "")
|
|
53
105
|
error = task.get("error", "")
|
|
@@ -60,7 +112,12 @@ def _render_task(task: dict[str, Any]) -> Text:
|
|
|
60
112
|
line.append(description)
|
|
61
113
|
if subagent_type:
|
|
62
114
|
line.append(f" [{subagent_type}]", style="dim")
|
|
63
|
-
|
|
115
|
+
# Show duration if timestamps available
|
|
116
|
+
duration = _calc_duration(started_at, completed_at)
|
|
117
|
+
if duration:
|
|
118
|
+
line.append(f" — {duration}", style="green")
|
|
119
|
+
else:
|
|
120
|
+
line.append(" — done", style="green")
|
|
64
121
|
if result:
|
|
65
122
|
line.append(f" ({result})", style="dim green")
|
|
66
123
|
else:
|
|
@@ -68,7 +125,11 @@ def _render_task(task: dict[str, Any]) -> Text:
|
|
|
68
125
|
line.append(description)
|
|
69
126
|
if subagent_type:
|
|
70
127
|
line.append(f" [{subagent_type}]", style="dim")
|
|
71
|
-
|
|
128
|
+
duration = _calc_duration(started_at, completed_at)
|
|
129
|
+
if duration:
|
|
130
|
+
line.append(f" — {duration}", style="red")
|
|
131
|
+
else:
|
|
132
|
+
line.append(" — failed", style="red")
|
|
72
133
|
if error:
|
|
73
134
|
line.append(f"\n {error}", style="red")
|
|
74
135
|
else:
|
|
@@ -76,6 +137,26 @@ def _render_task(task: dict[str, Any]) -> Text:
|
|
|
76
137
|
line.append(description)
|
|
77
138
|
if subagent_type:
|
|
78
139
|
line.append(f" [{subagent_type}]", style="dim")
|
|
79
|
-
|
|
140
|
+
# Live elapsed time for running tasks
|
|
141
|
+
if started_at:
|
|
142
|
+
try:
|
|
143
|
+
elapsed = time.time() - float(started_at) / 1000 # startedAt is typically ms
|
|
144
|
+
line.append(f" — {format_duration(elapsed)}", style="yellow")
|
|
145
|
+
except (ValueError, TypeError):
|
|
146
|
+
line.append(" — running", style="yellow")
|
|
147
|
+
else:
|
|
148
|
+
line.append(" — running", style="yellow")
|
|
80
149
|
|
|
81
150
|
return line
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _calc_duration(started_at, completed_at) -> str:
|
|
154
|
+
"""Calculate duration string from timestamps."""
|
|
155
|
+
if started_at is None or completed_at is None:
|
|
156
|
+
return ""
|
|
157
|
+
try:
|
|
158
|
+
start = float(started_at) / 1000 # ms to seconds
|
|
159
|
+
end = float(completed_at) / 1000
|
|
160
|
+
return format_duration(end - start)
|
|
161
|
+
except (ValueError, TypeError):
|
|
162
|
+
return ""
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from rich.text import Text
|
|
12
13
|
from textual.widgets import Static
|
|
13
14
|
|
|
14
15
|
# Nerd Font icon registry: panel_name → (nerd_font_icon, ascii_fallback)
|
|
@@ -25,6 +26,7 @@ PANEL_ICONS: dict[str, tuple[str, str]] = {
|
|
|
25
26
|
"debug": ("\uf188", "d"), # nf-fa-bug
|
|
26
27
|
"settings": ("\uf013", "S"), # nf-fa-gear
|
|
27
28
|
"tty": ("\uf120", ">"), # nf-fa-terminal
|
|
29
|
+
"progress": ("\uf200", "P"), # nf-fa-pie_chart
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
|
|
@@ -44,6 +46,66 @@ def get_panel_icon(panel_name: str, use_nerd_font: bool = True) -> str:
|
|
|
44
46
|
return entry[0] if use_nerd_font else entry[1]
|
|
45
47
|
|
|
46
48
|
|
|
49
|
+
def render_progress_bar(percent: int | float, width: int = 20, warn_high: bool = False) -> Text:
|
|
50
|
+
"""Render a Unicode progress bar with color based on percentage.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
percent: Value 0-100.
|
|
54
|
+
width: Number of bar characters (default 20).
|
|
55
|
+
warn_high: If True, use red at high values (for resource usage).
|
|
56
|
+
If False (default), use blue at 100% (for completion).
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Rich Text like ``[████████░░░░░░░░░░░░] 22%``
|
|
60
|
+
"""
|
|
61
|
+
percent = max(0, min(100, int(percent)))
|
|
62
|
+
filled = round(width * percent / 100)
|
|
63
|
+
empty = width - filled
|
|
64
|
+
|
|
65
|
+
if warn_high:
|
|
66
|
+
if percent < 50:
|
|
67
|
+
style = "green"
|
|
68
|
+
elif percent <= 80:
|
|
69
|
+
style = "yellow"
|
|
70
|
+
else:
|
|
71
|
+
style = "red"
|
|
72
|
+
else:
|
|
73
|
+
style = "blue"
|
|
74
|
+
|
|
75
|
+
bar = Text()
|
|
76
|
+
bar.append("[")
|
|
77
|
+
bar.append("█" * filled, style=style)
|
|
78
|
+
bar.append("░" * empty, style="dim")
|
|
79
|
+
bar.append(f"] {percent}%")
|
|
80
|
+
return bar
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def format_duration(seconds: int | float) -> str:
|
|
84
|
+
"""Format seconds into human-friendly duration string.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
``47s``, ``2m 14s``, ``1h 5m``.
|
|
88
|
+
"""
|
|
89
|
+
seconds = max(0, int(seconds))
|
|
90
|
+
if seconds < 60:
|
|
91
|
+
return f"{seconds}s"
|
|
92
|
+
minutes, secs = divmod(seconds, 60)
|
|
93
|
+
if minutes < 60:
|
|
94
|
+
return f"{minutes}m {secs}s"
|
|
95
|
+
hours, mins = divmod(minutes, 60)
|
|
96
|
+
return f"{hours}h {mins}m"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def humanize_theme(slug: str) -> str:
|
|
100
|
+
"""Convert a theme slug to a display name.
|
|
101
|
+
|
|
102
|
+
``princess-bride`` → ``Princess Bride``
|
|
103
|
+
"""
|
|
104
|
+
if not slug:
|
|
105
|
+
return ""
|
|
106
|
+
return slug.replace("-", " ").replace("_", " ").title()
|
|
107
|
+
|
|
108
|
+
|
|
47
109
|
class BasePanel(Static):
|
|
48
110
|
"""Base class for BikeRack TUI panels.
|
|
49
111
|
|