Skip to content

Commit 9596517

Browse files
authored
Merge pull request #50 from eduralph/feat/48-unify-discontinue-naming
refactor(signoff): unify discontinue naming — park → discontinue (#48)
2 parents 6c6a626 + e6ba305 commit 9596517

12 files changed

Lines changed: 65 additions & 65 deletions

File tree

template/PCDA/quality-cycle/02-cycle-artifacts.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ DO ── reads brief, writes ──▶ patch.diff + test + build-notes.md
2727
│ accept ──▶ mark PR ready / merge → cycle complete │
2828
│ iterate-to-Do ──▶ rebuild (back to DO with same brief) │
2929
│ iterate-to-Plan ──▶ revise brief (back to PLAN, same cycle) │
30-
park ──▶ discontinue — no transition, bundle out
30+
discontinue ──▶ no transitionbundle dropped from set
3131
└────────────────────────────────────────────────────────────────────┘
3232
3333
@@ -167,10 +167,10 @@ Assembled across Do (spec + claim) and Check (evidence + verdict). Its job: let
167167
168168
## 9. Check sign-off ← human completes Check here
169169
- Disposition confirmed / overridden:
170-
- Outcome: merged-wider | closed-<reason> | iterated-to-Do | iterated-to-Plan | parked
170+
- Outcome: merged-wider | closed-<reason> | iterated-to-Do | iterated-to-Plan | discontinued
171171
- Iteration delta (if iterating): <what the next Do or Plan must change
172172
for THIS cycle — distinct from process-level Act deltas, which go to §10;
173-
on a park, the rationale for discontinuing / where the work goes instead>
173+
on a discontinue, the rationale for discontinuing / where the work goes instead>
174174
- By / date:
175175
176176
## 10. Act candidates (hints for the next Act review)

template/PCDA/quality-cycle/03-cycle-automation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ SUMMARY.md §9 set → sign-off applied:
4646
accept → cycle COMPLETE (frozen bundle)
4747
iterate-to-Do → driver archives every Do+Check artifact into iteration-v<N>/; state ← PLANNED (re-run Do against same brief)
4848
iterate-to-Plan → driver archives the attempt (incl. brief.md) into iteration-v<N>/; state ← UNPLANNED (human authors a new brief.md, then Do re-runs)
49-
park → state ← DISCONTINUED (no transition, no archive; bundle deliberately abandoned and dropped from the active set)
49+
discontinue → state ← DISCONTINUED (no transition, no archive; bundle deliberately abandoned and dropped from the active set)
5050
```
5151

5252
The driver stops the issue at AWAITING_SIGNOFF every time — including on iteration. After sign-off, an accepted bundle is **frozen**: it becomes input for the *next* Act review (a separate, cross-cycle pass — see below).
@@ -84,7 +84,7 @@ Check is one beat with three components, each automating at a different level. C
8484
- **accept** → driver performs the sign-off-gated transitions: marks the draft PR ready, posts the §8 tracker comment, and (where the project's per-repo spec allows it) merges. The push and draft-PR-open may already have happened during Do or Check assembly — accept only performs the steps that *required* sign-off. Cycle closes; bundle frozen.
8585
- **iterate-to-Do** → driver archives `patch.diff`, the test, and the rest of the Do+Check downstream into `iteration-v<N>/` (preserving `brief.md`), state returns to PLANNED, driver re-invokes the builder. Same cycle.
8686
- **iterate-to-Plan** → driver archives the whole attempt — incl. `brief.md` — into `iteration-v<N>/`, state returns to UNPLANNED; the human authors a new `brief.md`, then Do re-runs. Same cycle.
87-
- **park** → driver records §9 and performs **no** transition or archive; state becomes DISCONTINUED (terminal) and the bundle drops out of the active/pending set. For work that, on inspection, doesn't fit the cycle (e.g. handled out-of-band by hand) — a deliberate abandon, independent of §6 (no C6 accept-guard). The human records why parked / where the work goes instead, like the iterate rationale.
87+
- **discontinue** → driver records §9 and performs **no** transition or archive; state becomes DISCONTINUED (terminal) and the bundle drops out of the active/pending set. For work that, on inspection, doesn't fit the cycle (e.g. handled out-of-band by hand) — a deliberate abandon, independent of §6 (no C6 accept-guard). The human records why discontinued / where the work goes instead, like the iterate rationale.
8888

8989
Optionally, the human jots §10 Act candidates while at the bundle — these are hints for the next Act review, not gates for this sign-off.
9090

template/src/pdca_harness/cli.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def main(argv: list[str] | None = None) -> int:
3838
p_init.add_argument("issue_id")
3939
p_init.add_argument("--from-brief", type=Path, help="copy this file as brief.md")
4040

41-
p_run = sub.add_parser("run", help="advance an issue to a parked state")
41+
p_run = sub.add_parser("run", help="advance an issue to a halted state")
4242
p_run.add_argument("issue_id")
4343

4444
p_flow = sub.add_parser("flow", help="continuous Claude-driven cycle (Plan→Do→Check[→publish]→Act)")
@@ -84,7 +84,7 @@ def main(argv: list[str] | None = None) -> int:
8484
g.add_argument("--accept", action="store_true", help="accept — merge wider")
8585
g.add_argument("--iterate-do", action="store_true", help="rebuild against same brief")
8686
g.add_argument("--iterate-plan", action="store_true", help="revise the brief")
87-
g.add_argument("--park", action="store_true",
87+
g.add_argument("--discontinue", action="store_true",
8888
help="discontinue — record §9, no transition, drop from the pending set")
8989
p_signoff.add_argument("--by", default="", help="who signed off")
9090
p_signoff.add_argument("--delta", default="", help="iteration delta note")
@@ -370,8 +370,8 @@ def _signoff(cfg: Config, args: argparse.Namespace) -> int:
370370
action = "iterate-do"
371371
elif args.iterate_plan:
372372
action = "iterate-plan"
373-
else: # --park: deliberate abandon, no C6 guard
374-
action = "park"
373+
else: # --discontinue: deliberate abandon, no C6 guard
374+
action = "discontinue"
375375

376376
date = datetime.date.today().isoformat()
377377
signoff.record(summary, action=action, by=args.by or "unknown", date=date, delta=args.delta)

template/src/pdca_harness/driver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def advance(d: Path, cfg: Config) -> None:
6666

6767

6868
def run_issue(d: Path, cfg: Config) -> str:
69-
"""Advance until the bundle reaches a parked state; return that state."""
70-
while state.state(d) not in state.PARKED:
69+
"""Advance until the bundle reaches a halted state; return that state."""
70+
while state.state(d) not in state.HALTED:
7171
advance(d, cfg)
7272
return state.state(d)
7373

template/src/pdca_harness/flow.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def flow(
150150
if not _plan_if_unplanned(cfg, d, csv):
151151
break
152152
if driver.run_issue(d, cfg) != state.AWAITING_SIGNOFF:
153-
break # reached COMPLETE, or parked somewhere the human must look at
153+
break # reached COMPLETE, or halted somewhere the human must look at
154154
if _signoff_and_apply(cfg, d, by=by, today=today) in (None, "blocked"):
155155
break
156156
if state.state(d) == state.COMPLETE:
@@ -415,16 +415,16 @@ def flow_batch(
415415
Runs the batch Plan session, then builds / checks / signs off EVERY bundle that
416416
has work left — the ones this session briefed AND any already in flight — so
417417
re-running ``flow --from-csv`` picks up where it left off instead of failing on
418-
"no new briefs". COMPLETE bundles (done), DISCONTINUED ones (parked) and UNPLANNED
418+
"no new briefs". COMPLETE bundles (done), DISCONTINUED ones (abandoned) and UNPLANNED
419419
ones (no brief — e.g. an issue the planner chose to skip) are left alone. Returns
420420
``{issue_id: state}``.
421421
"""
422422
today = today or datetime.date.today().isoformat()
423423

424424
leaves.do_plan_batch(cfg, csv)
425425
# Resume set: every bundle with a brief that isn't finished. UNPLANNED (skipped /
426-
# un-briefed), COMPLETE (done) and DISCONTINUED (parked — deliberately abandoned)
427-
# are excluded, so a re-run is idempotent and a parked bundle stays out of the sweep.
426+
# un-briefed), COMPLETE (done) and DISCONTINUED (deliberately abandoned)
427+
# are excluded, so a re-run is idempotent and a discontinued bundle stays out of the sweep.
428428
bundles = sorted(
429429
(cfg.bundle_root / name for name in _bundle_dirs(cfg)
430430
if state.state(cfg.bundle_root / name)
@@ -457,7 +457,7 @@ def flow_ids(
457457
Like :func:`flow_batch` but seeded by explicit ids with **no Plan beat** — the
458458
bundles must already have a brief. Missing / un-briefed (UNPLANNED) ids are
459459
skipped with a note (brief them at Plan first); terminal ids (COMPLETE or
460-
DISCONTINUED/parked) are left alone. Returns ``{issue_id: state}``.
460+
DISCONTINUED) are left alone. Returns ``{issue_id: state}``.
461461
"""
462462
today = today or datetime.date.today().isoformat()
463463
bundles: list[Path] = []

template/src/pdca_harness/leaves.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
# The interactive sign-off leaf writes its decision here; the flow reads it and
5353
# routes it through the C6-guarded signoff.record (never a model-written §9).
5454
SIGNOFF_DECISION = "signoff-decision"
55-
VALID_DECISIONS = frozenset({"accept", "iterate-do", "iterate-plan", "park"})
55+
VALID_DECISIONS = frozenset({"accept", "iterate-do", "iterate-plan", "discontinue"})
5656

5757

5858
# ----------------------------------------------------------------------------
@@ -410,7 +410,7 @@ def _signoff_prompt(d: Path) -> str:
410410
f"`- [ ]` to `- [x]` only with their explicit OK). Then write the agreed "
411411
f"decision as a single token — one of: {', '.join(sorted(VALID_DECISIONS))} — "
412412
f"into {d}/{SIGNOFF_DECISION}. For an iterate, add the rationale (why rejected / "
413-
f"what to change) on the lines below the token; for park, the rationale (why "
413+
f"what to change) on the lines below the token; for discontinue, the rationale (why "
414414
f"discontinued / where the work goes instead). Do not edit §9 yourself; the "
415415
"driver records it under a deterministic guard."
416416
)
@@ -426,7 +426,7 @@ def _stub_signoff(d: Path, cfg: Config) -> None:
426426

427427

428428
def run_signoff_batch(cfg: Config, bundles: list[Path]) -> None:
429-
"""Batch sign-off: ONE interactive session walks several parked bundles.
429+
"""Batch sign-off: ONE interactive session walks several halted bundles.
430430
431431
Mirrors :func:`do_plan_batch` — command mode runs a single seeded session over
432432
the whole (cheap-first) chunk, so the human signs off N bundles without N session

template/src/pdca_harness/queue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""The cheap-first sign-off queue (docs 03 §sign-off queue, §Batch fan-out).
22
33
After the driver fans out over a batch, the human works a burn-down: bundles
4-
parked at AWAITING_SIGNOFF, ordered so the near-instant confirms come first
4+
halted at AWAITING_SIGNOFF, ordered so the near-instant confirms come first
55
(empty §6 — typically already-fixed / wontfix / by-design) and the real
66
adjudications (non-empty §6 NEEDS-HUMAN) come last. This module is the pure
77
ordering logic; the CLI renders it.

template/src/pdca_harness/signoff.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
# mapping lives in :mod:`pdca_harness.state` (which owns the state names); this
1616
# module knows only the tokens, so there is no import cycle between the two.
1717
VALID_OUTCOMES = frozenset(
18-
{"merged-wider", "accepted", "iterated-to-Do", "iterated-to-Plan", "parked"})
18+
{"merged-wider", "accepted", "iterated-to-Do", "iterated-to-Plan", "discontinued"})
1919

20-
# What `signoff --accept/--iterate-do/--iterate-plan/--park` writes into the Outcome line.
20+
# What `signoff --accept/--iterate-do/--iterate-plan/--discontinue` writes into the Outcome line.
2121
ACTION_TO_OUTCOME = {
2222
"accept": "merged-wider",
2323
"iterate-do": "iterated-to-Do",
2424
"iterate-plan": "iterated-to-Plan",
25-
"park": "parked",
25+
"discontinue": "discontinued",
2626
}
2727

2828
_OUTCOME_RE = re.compile(r"^- Outcome:\s*(.*?)\s*$", re.MULTILINE)
@@ -77,7 +77,7 @@ def open_needs_human(summary_path: Path) -> list[str]:
7777
def record(summary_path: Path, *, action: str, by: str, date: str, delta: str = "") -> None:
7878
"""Write the human's §9 decision into ``SUMMARY.md`` in place.
7979
80-
``action`` is one of ``accept`` / ``iterate-do`` / ``iterate-plan`` / ``park``.
80+
``action`` is one of ``accept`` / ``iterate-do`` / ``iterate-plan`` / ``discontinue``.
8181
"""
8282
outcome = ACTION_TO_OUTCOME[action]
8383
text = summary_path.read_text(encoding="utf-8")

template/src/pdca_harness/state.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from . import signoff
1414

15-
# The ordered states a bundle moves through. The terminal/parked states
15+
# The ordered states a bundle moves through. The terminal/halted states
1616
# (UNPLANNED, AWAITING_SIGNOFF, COMPLETE) are where the driver stops and a human
1717
# acts; the rest the driver advances through unattended.
1818
UNPLANNED = "UNPLANNED" # no brief — human authors it (Plan)
@@ -23,10 +23,10 @@
2323
ITERATE_DO = "ITERATE_DO" # sign-off chose iterate-to-Do
2424
ITERATE_PLAN = "ITERATE_PLAN" # sign-off chose iterate-to-Plan
2525
COMPLETE = "COMPLETE" # sign-off accepted — bundle frozen
26-
DISCONTINUED = "DISCONTINUED" # sign-off chose park — deliberately abandoned, no transition
26+
DISCONTINUED = "DISCONTINUED" # sign-off chose discontinue — deliberately abandoned, no transition
2727

2828
# States where the driver does nothing (human work, or done).
29-
PARKED = {UNPLANNED, AWAITING_SIGNOFF, COMPLETE, DISCONTINUED}
29+
HALTED = {UNPLANNED, AWAITING_SIGNOFF, COMPLETE, DISCONTINUED}
3030

3131
# §9 outcome token → bundle state. state owns the state names, so the mapping
3232
# lives here; signoff knows only the tokens (no import cycle).
@@ -35,7 +35,7 @@
3535
"accepted": COMPLETE,
3636
"iterated-to-Do": ITERATE_DO,
3737
"iterated-to-Plan": ITERATE_PLAN,
38-
"parked": DISCONTINUED,
38+
"discontinued": DISCONTINUED,
3939
}
4040

4141

template/tests/test_driver_slice.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,32 +98,32 @@ def test_iterate_to_plan_archives_attempt(self) -> None:
9898
self.assertTrue((self.d / "iteration-v1" / "brief.md").exists()) # preserved
9999
self.assertTrue((self.d / "iteration-v1" / "patch.diff").exists()) # attempt preserved
100100

101-
def test_park_derives_discontinued_and_does_not_transition(self) -> None:
102-
# park is terminal: §9 records `parked`, state derives DISCONTINUED, and
101+
def test_discontinue_derives_discontinued_and_does_not_transition(self) -> None:
102+
# discontinue is terminal: §9 records `discontinued`, state derives DISCONTINUED, and
103103
# run_issue performs NO transition (no archive — the attempt stays in place,
104104
# the bundle just drops out of the active set).
105105
driver.run_issue(self.d, self.cfg)
106-
signoff.record(self.d / "SUMMARY.md", action="park", by="tester", date="2026-01-01")
107-
self.assertEqual(signoff.outcome_token(self.d / "SUMMARY.md"), "parked")
106+
signoff.record(self.d / "SUMMARY.md", action="discontinue", by="tester", date="2026-01-01")
107+
self.assertEqual(signoff.outcome_token(self.d / "SUMMARY.md"), "discontinued")
108108
self.assertEqual(state.state(self.d), state.DISCONTINUED)
109109
self.assertEqual(driver.run_issue(self.d, self.cfg), state.DISCONTINUED) # no-op
110110
self.assertFalse((self.d / "iteration-v1").exists()) # nothing archived
111111
self.assertTrue((self.d / "patch.diff").exists()) # attempt left untouched
112112

113-
def test_park_not_guarded_by_open_needs_human(self) -> None:
114-
# Parking is a deliberate abandon, independent of §6 — unlike accept (C6), a
115-
# bundle with open NEEDS-HUMAN items can still be parked at the record layer.
113+
def test_discontinue_not_guarded_by_open_needs_human(self) -> None:
114+
# Discontinue is a deliberate abandon, independent of §6 — unlike accept (C6), a
115+
# bundle with open NEEDS-HUMAN items can still be discontinued at the record layer.
116116
driver.run_issue(self.d, self.cfg)
117117
self.assertTrue(signoff.open_needs_human(self.d / "SUMMARY.md")) # §6 still open
118-
signoff.record(self.d / "SUMMARY.md", action="park", by="tester", date="2026-01-01")
118+
signoff.record(self.d / "SUMMARY.md", action="discontinue", by="tester", date="2026-01-01")
119119
self.assertEqual(state.state(self.d), state.DISCONTINUED)
120120

121-
def test_signoff_decision_accepts_park_token(self) -> None:
122-
# The `park` token was silently dropped before (#42); leaves now recognises it
121+
def test_signoff_decision_accepts_discontinue_token(self) -> None:
122+
# The `discontinue` token was silently dropped before (#42); leaves now recognises it
123123
# and reads the rationale written below it.
124124
(self.d / leaves.SIGNOFF_DECISION).write_text(
125-
"park\nrestructuring task — handled by hand upstream\n", encoding="utf-8")
126-
self.assertEqual(leaves.signoff_decision(self.d), "park")
125+
"discontinue\nrestructuring task — handled by hand upstream\n", encoding="utf-8")
126+
self.assertEqual(leaves.signoff_decision(self.d), "discontinue")
127127
self.assertEqual(leaves.signoff_rationale(self.d),
128128
"restructuring task — handled by hand upstream")
129129

@@ -305,7 +305,7 @@ def test_index_only_sees_frozen(self) -> None:
305305
live = self.cfg.bundle("LIVE")
306306
live.mkdir(parents=True)
307307
shutil.copyfile(TOY_BRIEF, live / "brief.md")
308-
driver.run_issue(live, self.cfg) # parks at AWAITING_SIGNOFF
308+
driver.run_issue(live, self.cfg) # halts at AWAITING_SIGNOFF
309309
names = [e.bundle.name for e in act.index(self.cfg)]
310310
self.assertEqual(names, ["issue_DONE"])
311311

0 commit comments

Comments
 (0)