@plot-pm/board 0.14.2 → 0.14.4

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-plan-meta.sh CHANGED
@@ -440,19 +440,35 @@ function reset_state() {
440
440
  delete changelog; n_changelog = 0; changelog_seen = 0; cl_open = 0
441
441
  }
442
442
  function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assignee, review, impl, design, approved, delivered, issue, issue2, i, j, v, is_dup, out, sorted_b, sorted_p, sorted_i, nb, np, ni, num_issues, str_issues, n_num_i, n_str_i, issue_is_str) {
443
- if (fm_status != "" || fm_phase != "") {
444
- fmt = "frontmatter"
445
- praw = (fm_status != "") ? fm_status : fm_phase
446
- palt_raw = (fm_status != "" && fm_phase != "") ? fm_phase : ""
447
- traw = fm_type
448
- } else if (canon_state != "" || canon_phase != "") {
443
+ # THE PHASE IS READ FROM THE FIELD PLOT WRITES. A canonical `State:`/`Phase:`
444
+ # outranks front matter, because every lifecycle script writes the canonical
445
+ # body and none writes front matter: `plot-approve.sh` holds zero front-matter
446
+ # references, and the five in `plot-deliver.sh` all REFUSE the case rather
447
+ # than write it. A plan carrying both therefore reported a value no transition
448
+ # had ever touched - measured 2026-09-17, an approval wrote `State: Approved`,
449
+ # the parser answered `draft`, and the scan dispatched nothing.
450
+ #
451
+ # `phase_alt` carries the loser, so the disagreement stays readable - that is
452
+ # the field the delivery gate in `plot-deliver.sh` reads.
453
+ #
454
+ # THE PRECEDENCE MOVES FOR THE FIELDS PLOT OWNS, NOT FOR EVERY FIELD. The
455
+ # board-facing fields below, `design_raw` among them, keep front-matter-wins:
456
+ # no lifecycle script writes any of them, so neither record is more current.
457
+ if (canon_state != "" || canon_phase != "") {
449
458
  # `State:` is primary and `Phase:` the alternate, exactly as front matter
450
459
  # reads `status:` over `phase:`. A file carrying both reports the
451
- # disagreement rather than hiding it.
460
+ # disagreement rather than hiding it, and front matter joins that alternate
461
+ # when the canonical body answered first.
452
462
  fmt = "canonical"
453
463
  praw = (canon_state != "") ? canon_state : canon_phase
454
464
  palt_raw = (canon_state != "" && canon_phase != "") ? canon_phase : ""
465
+ if (palt_raw == "") palt_raw = (fm_status != "") ? fm_status : fm_phase
455
466
  traw = canon_type
467
+ } else if (fm_status != "" || fm_phase != "") {
468
+ fmt = "frontmatter"
469
+ praw = (fm_status != "") ? fm_status : fm_phase
470
+ palt_raw = (fm_status != "" && fm_phase != "") ? fm_phase : ""
471
+ traw = fm_type
456
472
  } else {
457
473
  fmt = "none"; praw = ""; palt_raw = ""; traw = ""
458
474
  }
package/plot-reap.sh CHANGED
@@ -385,11 +385,19 @@ manifest_for() {
385
385
  reap=0; kept=0; removed=0; cleared=0; vanished=0; unplaced=0
386
386
  printf '%-8s %-52s %s\n' "verdict" "branch" "why"
387
387
 
388
- while IFS=$'\t' read -r wt br prunable; do
388
+ while IFS=$'\037' read -r wt br prunable; do
389
389
  [ -n "$wt" ] || continue
390
390
  short=${br#refs/heads/}
391
391
  [ "$wt" = "$ROOT" ] && continue
392
392
 
393
+ # WHAT THE REPORT CALLS THIS TREE. `short` is the branch and stays empty for
394
+ # a detached desk, which is the correct reading and a useless label — an
395
+ # operator reading a blank column cannot tell which of thirteen it names.
396
+ # The directory is what identifies such a tree, so the report says so and
397
+ # every refusal still measures `short`.
398
+ label=$short
399
+ [ -z "$label" ] && label="(detached) $(basename "$wt")"
400
+
393
401
  # 4a. GIT'S OWN ANSWER THAT THE DIRECTORY IS GONE, and it is a REPORT rather
394
402
  # than a sixth refusal. The five below each say *do not remove this* and
395
403
  # send an operator to look; this says *there is nothing to remove and the
@@ -414,7 +422,7 @@ while IFS=$'\t' read -r wt br prunable; do
414
422
  # prune stays the operator's decision — the same discipline that makes
415
423
  # every refusal a measurement rather than an act.
416
424
  if [ "$prunable" = "yes" ]; then
417
- printf '%-8s %-52s %s\n' "vanished" "$short" "directory gone — 'git worktree prune' clears the entry"
425
+ printf '%-8s %-52s %s\n' "vanished" "$label" "directory gone — 'git worktree prune' clears the entry"
418
426
  vanished=$((vanished+1)); continue
419
427
  fi
420
428
 
@@ -482,7 +490,7 @@ while IFS=$'\t' read -r wt br prunable; do
482
490
  # population the rule excludes. It counts, because a refusal that counts is
483
491
  # the difference between *nothing to clean* and *nothing was looked at*.
484
492
  if [ "$unclassified" = true ]; then
485
- printf '%-8s %-52s %s\n' "unknown" "$short" \
493
+ printf '%-8s %-52s %s\n' "unknown" "$label" \
486
494
  "under $(basename "$WT_ROOT")/, no worker pid and no recognised name — needs a person"
487
495
  unplaced=$((unplaced+1)); continue
488
496
  fi
@@ -531,6 +539,25 @@ while IFS=$'\t' read -r wt br prunable; do
531
539
  merge=merged; why="merged into $DEFAULT"
532
540
  elif [ -n "$short" ] && pr_merged "$short"; then
533
541
  merge=merged; why="PR merged (squash)"
542
+ elif [ -z "$short" ] \
543
+ && [ "$(git -C "$wt" rev-list --count "origin/$DEFAULT..HEAD" 2>/dev/null || echo 1)" = "0" ]; then
544
+ # A DETACHED DESK HAS NOTHING TO LAND, and that is a measurement rather
545
+ # than a weakened refusal. `plot-dispatch.sh --start` cuts a free agent's
546
+ # desk detached at `origin/<main>` because a free agent holds no slice, so
547
+ # such a tree never had a branch and can never have a PR. Reading it
548
+ # through `no-merged-pr` would refuse every one of them forever — the same
549
+ # silence this slice removes, in the opposite direction.
550
+ #
551
+ # THE READING IS `HEAD` AGAINST THE DEFAULT BRANCH, never the desk's name.
552
+ # Measured 2026-09-22: three `free-*` desks hold a branch and two carry
553
+ # live workers (pids 27820, 6542), so a prefix-keyed test reaps a running
554
+ # agent. Detachment is the condition; the name is not.
555
+ #
556
+ # A detached desk carrying commits falls through to `not-merged` and is
557
+ # kept, which is correct: somebody committed there and nothing says the
558
+ # work landed. The live-pid, marker and dirty refusals are all asked
559
+ # before this and are unaffected.
560
+ merge=merged; why="detached, nothing to land"
534
561
  fi
535
562
 
536
563
  # THE DECISION. One call, and the script holds no `if` about whether a
@@ -599,11 +626,11 @@ NODE_EOF
599
626
  no-merged-pr) reason="unlanded work — no merged PR" ;;
600
627
  *) reason="rule could not be asked — keeping" ;;
601
628
  esac
602
- printf '%-8s %-52s %s\n' "keep" "$short" "$reason"; kept=$((kept+1)); continue
629
+ printf '%-8s %-52s %s\n' "keep" "$label" "$reason"; kept=$((kept+1)); continue
603
630
  fi
604
631
 
605
632
  if [ "$MAX" -gt 0 ] && [ "$reap" -ge "$MAX" ]; then
606
- printf '%-8s %-52s %s\n' "keep" "$short" "--max $MAX reached"; kept=$((kept+1)); continue
633
+ printf '%-8s %-52s %s\n' "keep" "$label" "--max $MAX reached"; kept=$((kept+1)); continue
607
634
  fi
608
635
 
609
636
  # Resolved BEFORE the removal, because `canonical` needs the directory to
@@ -619,7 +646,7 @@ NODE_EOF
619
646
 
620
647
  reap=$((reap+1))
621
648
  if [ "$DRY" -eq 1 ]; then
622
- printf '%-8s %-52s %s\n' "would" "$short" "$why${logs:+, log $logs}"
649
+ printf '%-8s %-52s %s\n' "would" "$label" "$why${logs:+, log $logs}"
623
650
  else
624
651
  if git worktree remove --force "$wt" 2>/dev/null; then
625
652
  # The worktree is gone; NOW the manifest may go. Inside the success arm
@@ -647,16 +674,16 @@ NODE_EOF
647
674
  while IFS= read -r f; do rm -f "$f" 2>/dev/null; done < <(branch_log_files "$short")
648
675
  why="$why, log removed"
649
676
  fi
650
- printf '%-8s %-52s %s\n' "reaped" "$short" "$why"; removed=$((removed+1))
677
+ printf '%-8s %-52s %s\n' "reaped" "$label" "$why"; removed=$((removed+1))
651
678
  else
652
- printf '%-8s %-52s %s\n' "FAILED" "$short" "git worktree remove refused"; kept=$((kept+1))
679
+ printf '%-8s %-52s %s\n' "FAILED" "$label" "git worktree remove refused"; kept=$((kept+1))
653
680
  fi
654
681
  fi
655
682
  done < <(git worktree list --porcelain \
656
- | awk '/^worktree /{ if (br != "") print p"\t"br"\t"pr; p=$2; br=""; pr="no"; next }
683
+ | awk -v OFS="\037" '/^worktree /{ if (p != "") print p, br, pr; p=$2; br=""; pr="no"; next }
657
684
  /^branch / { br=$2; next }
658
685
  /^prunable/ { pr="yes"; next }
659
- END { if (br != "") print p"\t"br"\t"pr }')
686
+ END { if (p != "") print p, br, pr }')
660
687
 
661
688
  [ "$DRY" -eq 0 ] && git worktree prune 2>/dev/null
662
689