@plot-pm/board 0.11.0 → 0.13.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/plot-deliver.sh CHANGED
@@ -35,7 +35,7 @@
35
35
  #
36
36
  # Each step asks THE SOURCE IT WOULD HAVE WRITTEN whether it is already done:
37
37
  # the plan file for the phase and the record, the index directories for the
38
- # symlink, the sprint file for the annotation. Never a progress file of its own.
38
+ # symlink, the sprint file for the tick. Never a progress file of its own.
39
39
  #
40
40
  # WHAT IT REFUSES, and why refusing beats guessing:
41
41
  # - phase is not `approved` — nothing to deliver. (Already-Delivered is NOT a
@@ -66,7 +66,7 @@ done
66
66
  die() { echo "plot-deliver: $*" >&2; exit 1; }
67
67
 
68
68
  [ -n "$slug" ] || die "need a plan slug (usage: plot-deliver.sh [--dry-run] <slug>)"
69
- git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository"
69
+ git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository — run this from inside the checkout, or 'git init' one here"
70
70
 
71
71
  cfg() { bash "$script_dir/plot-config.sh" get "$1" "$2"; }
72
72
 
@@ -86,10 +86,13 @@ plan_file=""
86
86
  for cand in "$PLAN_DIR"*"$slug".md "$ACTIVE_DIR$slug.md" "$DELIVERED_DIR$slug.md"; do
87
87
  [ -e "$cand" ] && { plan_file="$cand"; break; }
88
88
  done
89
- [ -n "$plan_file" ] || die "no plan found for '$slug' — looked in $PLAN_DIR, $ACTIVE_DIR, $DELIVERED_DIR"
89
+ [ -n "$plan_file" ] || die "no plan found for '$slug' — looked in $PLAN_DIR, $ACTIVE_DIR, $DELIVERED_DIR.
90
+ Check the slug: ls $PLAN_DIR | grep -i '$slug'"
90
91
 
91
92
  meta=$(bash "$script_dir/plot-plan-meta.sh" "$plan_file" 2>/dev/null) || meta=""
92
- [ -n "$meta" ] || die "cannot parse '$plan_file' — refusing rather than guessing"
93
+ [ -n "$meta" ] || die "cannot parse '$plan_file' — refusing rather than guessing.
94
+ See what the parser reads: $script_dir/plot-plan-meta.sh $plan_file
95
+ A plan needs a '## Status' section with a 'State:' field."
93
96
 
94
97
  jfield() { printf '%s' "$meta" | jq -r "$1" 2>/dev/null; }
95
98
 
@@ -106,13 +109,16 @@ delivered_raw=$(jfield '.delivered_raw')
106
109
  case "$phase" in
107
110
  approved|delivered) ;;
108
111
  released)
109
- die "plan '$slug' is already released — nothing to deliver." ;;
112
+ die "plan '$slug' is already released — nothing to deliver.
113
+ Nothing to do here; the work shipped." ;;
110
114
  draft|design)
111
- die "plan '$slug' is still '$phase' — approve it first." ;;
115
+ die "plan '$slug' is still '$phase' — approve it first: /plot-approve $slug" ;;
112
116
  NONE|"")
113
- die "cannot read the phase of '$slug' ($plan_file) — refusing rather than guessing." ;;
117
+ die "cannot read the phase of '$slug' ($plan_file) — refusing rather than guessing.
118
+ Its '## Status' section needs a line reading '- **State:** Approved'." ;;
114
119
  *)
115
- die "plan '$slug' is in phase '$phase' — only an Approved plan can be delivered." ;;
120
+ die "plan '$slug' is in phase '$phase' — only an Approved plan can be delivered.
121
+ If that phase is wrong, correct the 'State:' line in $plan_file and push it." ;;
116
122
  esac
117
123
 
118
124
  # ---------------------------------------------------------------------------
@@ -157,7 +163,8 @@ fi
157
163
 
158
164
  verdict=$(PLOT_REPO_ROOT="$repo_root" PLOT_SCRIPTS_DIR="$script_dir" \
159
165
  node "$ask_mjs" deliverable "$slug" "$plan_file" 2>/dev/null) || verdict=""
160
- [ -n "$verdict" ] || die "cannot determine deliverability of '$slug' — refusing rather than guessing."
166
+ [ -n "$verdict" ] || die "cannot determine deliverability of '$slug' — refusing rather than guessing.
167
+ See what it said: PLOT_REPO_ROOT=$repo_root PLOT_SCRIPTS_DIR=$script_dir node $ask_mjs deliverable $slug $plan_file"
161
168
 
162
169
  vfield() { printf '%s' "$verdict" | jq -r "$1" 2>/dev/null; }
163
170
 
@@ -187,7 +194,7 @@ today=$(date +%Y-%m-%d)
187
194
  if [ "$dry_run" = 1 ]; then
188
195
  echo "step: would flip Phase → Delivered and fill Delivered: $today"
189
196
  echo "step: would move active/ → delivered/ symlink"
190
- echo "step: would update the sprint annotation${sprint:+ (sprint: $sprint)}"
197
+ echo "step: would tick the sprint item${sprint:+ (sprint: $sprint)}"
191
198
  echo "summary: phase=would record=would index=would sprint=would push=would"
192
199
  exit 0
193
200
  fi
@@ -221,12 +228,15 @@ real_plan_path() { # $1 = plan file as found
221
228
  }
222
229
 
223
230
  rel=$(cd "$repo_root" && real_plan_path "$plan_file") || rel=""
224
- [ -n "$rel" ] || die "$plan_file is outside the repository root"
231
+ [ -n "$rel" ] || die "$plan_file is outside the repository root ($repo_root).
232
+ Move the plan under $PLAN_DIR inside this checkout and re-run."
225
233
 
226
234
  # The filename, for symlink creation.
227
235
  plan_basename=$(basename "$rel")
228
236
 
229
- # Flip `**Phase:** Approved` → `Delivered` in the `## Status` section only.
237
+ # Flip `**State:** Approved` → `Delivered` in the `## Status` section only.
238
+ # Reads `State:` and `Phase:` alike: it changes the VALUE on whichever line
239
+ # carries it, so a plan written before the 2026-09-07 rename still delivers.
230
240
  #
231
241
  # READS ONE FILE AND WRITES ANOTHER, rather than editing in place. It edited in
232
242
  # place until 2026-09-02, which is what let the phase land without its record —
@@ -235,7 +245,7 @@ flip_phase() { # $1=in $2=out → 0 if it changed the file, 1 if nothing to fli
235
245
  awk '
236
246
  BEGIN { section = ""; done = 0 }
237
247
  /^## / { section = ($0 ~ /^## Status/) ? "status" : ""; print; next }
238
- section == "status" && !done && tolower($0) ~ /^[ \t]*[-*]?[ \t]*\**phase[:*]/ {
248
+ section == "status" && !done && tolower($0) ~ /^[ \t]*[-*]?[ \t]*\**(state|phase)[:*]/ {
239
249
  if (tolower($0) ~ /approved/) {
240
250
  sub(/[Aa]pproved/, "Delivered")
241
251
  done = 1
@@ -410,8 +420,20 @@ write_transition() { # $1=file $2=record $3=recorded(yes|no) → sets phase_repo
410
420
  return 0
411
421
  }
412
422
 
413
- # Update the sprint item annotation for this plan.
414
- update_sprint_annotation() { # $1=worktree root → prints none|updated|already|missing
423
+ # Tick this plan's sprint item.
424
+ #
425
+ # THE TICK ONLY, since 2026-09-08. This also wrote a `<!-- status: delivered -->`
426
+ # annotation, and `a-withdrawn-item-is-not-open` measured that record dead in
427
+ # both directions: 67 lines carried one, none carried a value any reader acted
428
+ # on, and `plot-sprint-release.sh` read the field nowhere. The plan file carries
429
+ # `State:` and a dated `Delivered:` record, and the estate-outranks-the-checkbox
430
+ # rule points at those — a cache nobody refreshes and nobody reads is a second
431
+ # answer waiting to contradict the first.
432
+ #
433
+ # The box is NOT the same record. `/plot-sprint close` step 2a exists to tick
434
+ # boxes the estate says are done, and ticking here is what keeps that step's
435
+ # work to the items it genuinely cannot resolve.
436
+ update_sprint_item() { # $1=worktree root → prints none|updated|already|missing
415
437
  local root="$1" f found=""
416
438
  [ -n "$sprint" ] || { printf 'none'; return 0; }
417
439
  local dir="$root/${SPRINT_DIR#/}"
@@ -429,15 +451,8 @@ update_sprint_annotation() { # $1=worktree root → prints none|updated|already|
429
451
  index($0, "[" slug "]") == 0 { print; next }
430
452
  {
431
453
  line = $0
432
- # Check the box
454
+ # Check the box. Nothing else on the line is touched.
433
455
  sub(/\[ \]/, "[x]", line)
434
- # Update or add status annotation
435
- if (index(line, "<!--") == 0) {
436
- line = line " <!-- status: delivered -->"
437
- } else {
438
- if (line ~ /status:[ \t]*[a-z-]+/) sub(/status:[ \t]*[a-z-]+/, "status: delivered", line)
439
- else sub(/-->/, ", status: delivered -->", line)
440
- }
441
456
  print line
442
457
  }
443
458
  ' "$found")
@@ -500,8 +515,8 @@ apply_local_writes() { # $1=root → sets phase_report record_report index_repo
500
515
  # Step 5 — move the index symlink (best effort).
501
516
  index_report=$(move_index_symlink "$root")
502
517
 
503
- # Step 6 — update the sprint annotation.
504
- sprint_report=$(update_sprint_annotation "$root")
518
+ # Step 6 — tick the sprint item.
519
+ sprint_report=$(update_sprint_item "$root")
505
520
  return 0
506
521
  }
507
522
 
@@ -515,7 +530,10 @@ bookbr="plot/deliver-$slug"
515
530
  tmpwt="$wt_root/.plot-deliver-$slug.$$"
516
531
  # -B: a leftover branch from an earlier failed run must not block this one.
517
532
  git worktree add -q -B "$bookbr" "$tmpwt" "origin/$MAIN" 2>/dev/null \
518
- || die "could not prepare a booking worktree at $tmpwt"
533
+ || die "could not prepare a booking worktree at $tmpwt.
534
+ Most often origin/$MAIN is not fetched, or '$bookbr' is checked out in
535
+ another worktree. Check both: git fetch origin $MAIN && git worktree list
536
+ Nothing has been written locally; the plan is untouched."
519
537
 
520
538
  cleanup() {
521
539
  git worktree remove --force "$tmpwt" >/dev/null 2>&1 || true
@@ -542,7 +560,9 @@ if git -C "$tmpwt" diff --cached --quiet 2>/dev/null; then
542
560
  else
543
561
  if ! git -C "$tmpwt" -c "user.name=$who" commit -q -m "plot: deliver $slug"; then
544
562
  cleanup
545
- die "could not commit the delivery"
563
+ die "could not commit the delivery.
564
+ See what git refused: git -C $tmpwt status
565
+ Nothing was pushed; re-run this — it is idempotent."
546
566
  fi
547
567
 
548
568
  push_out=$(bash "$script_dir/plot-push-main.sh" "$bookbr" "$MAIN" 2>&1)