Derived from PI-1/26 planning session (2026-04-07). Baseline: 109 BDD scenarios passing, 20 failing (behave runner).
Files: pyproject.toml, Makefile → justfile, tests/
Context: iacre (the reference project) uses pytest-bdd with scenarios() wiring,
uv run pytest, and a justfile test ladder. tpcli has both behave and pytest-bdd
installed but uses behave as runner. Migrating aligns the two repos and enables
pytest fixtures, markers, and parametrize across unit + BDD in one run.
Acceptance Criteria:
-
justfilecreated with same ladder structure as iacre:test-fast→test-unit→test-bdd→test-slow→test-e2e - All existing behave
.featurefiles intests/features/converted to pytest-bdd:scenarios()wiring intests/bdd/test_*.py, steps intests/bdd/steps/ -
tests/bdd/features/{domain}/directory layout mirrors iacre -
@tagsadded:@unit,@bdd,@slow,@e2e,@P0/@P1/@P2 -
uv run pytest tests/unit/ tests/bdd/ -m "not slow"is the fast loop - Makefile kept as thin wrapper calling justfile (backwards compat)
- Baseline: same 109 passing scenarios, 20 failing — no regressions
BDD: Not applicable (this IS the BDD infrastructure)
Files: tests/bdd/features/plan/update.feature,
tests/bdd/steps/plan/update_steps.py
Context: client.go Update() builds /api/v1/Feature/123 (singular → 404).
The existing go_cli_create_update.feature scenario even asserts the wrong URL
(PUT to /api/v1/TeamPIObjective/12345). Write the correct failing scenario first.
Red scenario:
@P0 @plan @update
Scenario: Update uses plural entity type in URL path
Given a Feature with Id 2284623 exists in TargetProcess
When I run: tpcli plan update Feature 2284623 --data '{"Name":"x"}'
Then the API request is PUT to /api/v1/Features/2284623
And the command succeeds with exit code 0Acceptance Criteria (red phase):
- Scenario exists and FAILS against current implementation
- Existing wrong assertion (
/api/v1/TeamPIObjective/12345) updated to fail correctly
Files: tests/bdd/features/plan/create_feature.feature
Red scenario:
@P0 @plan @create
Scenario: Create Feature with Team and Release persists nested objects
When I run: tpcli plan create Feature --data '{"Name":"Test","Team":{"Id":2022903},"Release":{"Id":2033368},"Project":{"Id":223264}}'
Then the command succeeds with exit code 0
And the created Feature has Release.Name "DAD: PI-1/26"
And the created Feature has Team.Name "DAD - Fusion - Cloud Enablement & Delivery"Acceptance Criteria (red phase):
- Scenario exists and FAILS (Release and Team are nil after create)
Files: tests/bdd/features/plan/link.feature
Red scenario:
@P0 @plan @link
Scenario: Link a Feature to a TeamPIObjective via join entity
Given Feature 2284623 and TeamPIObjective 2284608 exist in TargetProcess
When I run: tpcli plan link Feature 2284623 TeamPIObjective 2284608
Then a TeamPIObjectiveFeature record is created
And GET /api/v1/TeamPIObjective/2284608/Features includes Feature 2284623
Scenario: Unlink a Feature from a TeamPIObjective
Given Feature 2284623 is linked to TeamPIObjective 2284608
When I run: tpcli plan unlink Feature 2284623 TeamPIObjective 2284608
Then the TeamPIObjectiveFeature record is deleted
And GET /api/v1/TeamPIObjective/2284608/Features does not include Feature 2284623Acceptance Criteria (red phase):
- Both scenarios exist and FAIL (command not found)
Files: tests/unit/test_config.py, tests/bdd/features/config/context.feature
Red scenarios:
@P0 @config @context
Scenario: Load default context from config
Given config has current-context "dad-ced-pi-26-1"
And context "dad-ced-pi-26-1" has team "DAD - Fusion - Cloud Enablement & Delivery"
When I call get_default_team()
Then the result is "DAD - Fusion - Cloud Enablement & Delivery"
Scenario: Switch context
Given config has contexts "dad-ced-pi-25-4" and "dad-ced-pi-26-1"
When I run: tpcli context use dad-ced-pi-25-4
Then current-context in config is "dad-ced-pi-25-4"
And get_default_team() returns the PI-25-4 team
Scenario: Config migrate preserves credentials
Given a flat config with default-team and default-art
When I run: tpcli config migrate
Then a context is created with those values
And credentials (token, url) are unchanged at root levelAcceptance Criteria (red phase):
- All 3 scenarios FAIL against current flat-config implementation
Files: tpcli_pi/core/config.py, pkg/config/ (Go),
~/.config/tpcli/config.yaml
Context: See US-13-RED above. kubectl-style contexts. Key naming:
{org}-{team}-pi-{yy}-{n} (e.g. dad-ced-pi-26-1) sorts alphabetically =
chronologically. All keys within blocks sort alphabetically.
Acceptance Criteria:
-
load_config()readscurrent-contextand resolves context fields -
get_default_team(),get_default_art(), etc. read from active context - Flat
default-team/default-artstill work with deprecation warning - Go
pkg/config/reads same schema via viper -
tpcli config migrateconverts flat → context non-destructively - All US-13-RED scenarios GREEN
Files: cmd/context.go, tpcli_pi/cli/context.py
Acceptance Criteria:
-
tpcli context list— lists contexts, marks current with* -
tpcli context use <name>— writescurrent-contextto config -
tpcli context show— prints resolved fields for active context -
tpcli context add <name> --from <existing>— clones, shifts release -
--context <name>global flag overrides for one command - ID resolution lazy-cached in
~/.cache/tpcli/not in config - BDD scenarios GREEN
Files: pkg/tpclient/client.go
Context: Update() path: /api/v1/{entityType}/{id} — entityType is singular
(e.g. Feature). TP API expects plural (Features). Fix: pluralise before building
path. Create() uses singular POST which works — don't change it.
Acceptance Criteria:
-
Update()pluralises entity type:Feature→Features,TeamPIObjective→TeamPIObjectives - Existing wrong assertion in
go_cli_create_update.featurecorrected - Go unit test:
TestClientUpdatePluralPath - US-1-RED scenario GREEN
-
make test-gopasses
Files: pkg/tpclient/client.go, cmd/plan.go
Context: TP API silently ignores Team and Release on POST for Feature.
Workaround: after create, POST to /api/v1/Feature/{id} with {Team, Release}.
Acceptance Criteria:
-
Create()for Feature auto-issues follow-up POST ifTeamorReleasein payload - Follow-up POST uses correct singular endpoint (
/api/v1/Feature/{id}) - US-2-RED scenario GREEN
- Verified by GET after create
Files: cmd/plan.go, pkg/tpclient/client.go
Context: Join entity is TeamPIObjectiveFeature.
POST /api/v1/TeamPIObjectiveFeatures with {TeamPIObjective:{Id}, Feature:{Id}}.
Unlink: GET join entity ID, then DELETE /api/v1/TeamPIObjectiveFeatures/{id}.
Parallel: ProgramPIObjectiveFeature for ART-level objectives.
Acceptance Criteria:
-
tpcli plan link Feature <fid> TeamPIObjective <oid>→ creates join, prints Id -
tpcli plan unlink Feature <fid> TeamPIObjective <oid>→ finds + deletes join -
tpcli plan link --verify→ reads/TeamPIObjective/{oid}/Features, confirms present -
ProgramPIObjectiveFeaturesupported with same syntax - US-3-RED scenarios GREEN
Files: cmd/plan.go
Acceptance Criteria:
-
--quietprintsId: <n> Name: <name>only - Default (full JSON) unchanged
- Works for
createandupdate
Files: tpcli_pi/core/api_client.py — get_team()
Context: Config Cloud Enablement & Delivery ≠ TP name
DAD - Fusion - Cloud Enablement & Delivery. Ext scripts fail with "Team not found".
Acceptance Criteria:
- Exact match first; falls back to suffix/contains (case-insensitive)
- >1 match → error listing candidates
- 0 matches → error with
tpcli list Teamssuggestion - Works for config
default-teamand--teamflag - BDD scenario with short name → correct team; ambiguous → error
Files: tpcli_pi/core/api_client.py — create_feature()
Context: Currently 3 API calls needed: create, fix Release+Team, create join entity. Should be one logical operation with rollback.
Acceptance Criteria:
-
create_feature(..., objective_id=X)handles all 3 steps - Rollback (delete feature) if step 2 or 3 fails
-
tpcli plan create Feature --data '...' --link-objective <id>CLI flag - BDD: create with objective → verify Release, Team, and link all set
Files: pkg/tpclient/client.go or cmd/list.go
Acceptance Criteria:
- Root cause of API 400 on
--wherefor TeamPIObjectives identified - Fixed or documented with workaround in skill quick-ref
- If fixed:
tpcli list TeamPIObjectives --where "Release.Id eq X and Team.Id eq Y"works
Files: cmd/find.go or cmd/list.go
Acceptance Criteria:
-
tpcli find team "Cloud"fetches all teams, filters locally, returns matches -
--exactflag for strict match - Documented in skill quick-ref
Files: tpcli_pi/cli/compare.py (new ext script)
Context: PI-to-PI objective comparison — carry-over, dropped, new, status delta.
Acceptance Criteria:
-
tpcli plan compare --release "DAD: PI-1/26" --previous "DAD: PI-4/25" --team "..." - Columns: Objective | prev-status | curr-status | delta (carry-over/new/dropped)
-
--format markdownfor pasting into planning docs - Uses
previous-releasefrom active context if--previousnot given
Files: ~/.claude/skills/targetprocess/references/targetprocess-quick-ref.md,
~/.claude/skills/targetprocess/references/team-scenarios.md,
.github/skills/targetprocess/ (this repo),
~/shalomb/tpcli/.github/skills/targetprocess/
Acceptance Criteria:
-
TeamPIObjectiveFeaturejoin entity: POST pattern, GET verification, DELETE unlink - Required
Project: GMSGQfor all DAD entity creation - Correct full team name
DAD - Fusion - Cloud Enablement & Delivery -
tpcli list Teams --wherebroken — use--take 200+ grep workaround -
tpcli list TeamPIObjectivessorts oldest-first — use--skip Nto find new items - Jira
customfield_12902= TP entity URL (auto-synced viaJira Keycustom field on Feature) - DAD creating objectives recipe: full minimal payload with Project, Team, Release
- Context config schema documented (US-13)
-
rsync -av ~/.claude/skills/targetprocess/ .github/skills/targetprocess/ -
rsync -av ~/.claude/skills/targetprocess/ ~/shalomb/tpcli/.github/skills/targetprocess/
US-0 (pytest-bdd migration)
└── US-1-RED, US-2-RED, US-3-RED, US-13-RED (write failing tests)
└── US-13 (config schema — Python + Go)
└── US-12 (context command)
├── US-1 (fix plural endpoint)
├── US-2 (fix nested objects)
└── US-3 (plan link/unlink)
├── US-6 (--quiet flag)
├── US-4 (fuzzy team resolution)
└── US-10 (atomic feature create)
├── US-7 (where filter)
├── US-8 (find team)
└── US-9 (plan compare)
└── US-5/11 (skill update + sync)
Every feature file must score ≥ 7.0 on both indexes before merge:
- Adzic: Business-Readable, Declarative, Intention-Revealing, Focused, Living, Consistent
- Farley: Fast, Maintainable, Repeatable, Atomic, Necessary, Understandable
HTTP interactions mocked via responses (Python) / httptest (Go) — no real API calls
in unit or BDD tiers. Real API calls only in @e2e marked scenarios.