@plot-pm/board 0.14.0 → 0.14.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plot-pm/board",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Local Kanban board for Plot — a glanceable view of plan phases from docs/plans, with sprint and story filters",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/plot-deliver.sh CHANGED
@@ -419,6 +419,64 @@ decide_transition() { # $1=file → prints "<Phase>\t<record>\t<write|already>"
419
419
  # writes — and appending a second is how one plan came to hold two `Delivered:`
420
420
  # lines (2026-09-01). The domain returns the written record unchanged in that
421
421
  # case, so the phase still flips and nothing is inserted.
422
+ # Would the PARSER read this phase out of the file we are about to land?
423
+ #
424
+ # THE WRITE SUCCEEDING IS NOT THE OUTCOME HOLDING, and that gap is #924. A plan
425
+ # carrying BOTH front matter and a `## Status` block was delivered in a project
426
+ # repo: `flip_phase` wrote `Delivered` into the block, `mv` landed it, the
427
+ # summary said `phase=flipped`, and `plot-plan-meta.sh` went on answering
428
+ # `approved` — because it prefers front matter wherever it exists and reads the
429
+ # block only in the `else if` below. The write took effect on bytes nobody
430
+ # reads.
431
+ #
432
+ # `flip_phase`'s awk matches only inside `section == "status"`. That one guard
433
+ # IS the defect: on a front-matter plan it edits the block and leaves the front
434
+ # matter untouched, and returns 0 for having changed something.
435
+ #
436
+ # SO THE TEST IS WHAT THE PARSER ANSWERS, NEVER WHETHER AWK CHANGED A LINE.
437
+ # Refusing on `flipped=0` would break every re-run of a correct delivery — a
438
+ # plan already carrying `Delivered` flips nothing and is fine. This asks the one
439
+ # question that distinguishes them: read the scratch copy the way every later
440
+ # consumer will read the plan, and compare.
441
+ #
442
+ # IT RUNS ON THE SCRATCH COPY, BEFORE THE `mv`, and the caller passes whichever
443
+ # file that arm is about to land — `$a` on the `recorded=yes` arm, `$b` on the
444
+ # other. Parsing `$a` on the record arm would check content that never reaches
445
+ # the plan. After the `mv` is too late twice over: the script's own header
446
+ # documents exit 0 as *"the plan is Delivered on the default branch"*, so
447
+ # refusing there would exit 1 on a run meeting the documented success
448
+ # condition — and `runAutoDeliver` spawns this detached, logging a non-zero exit
449
+ # to nobody while the plan sits delivered on main.
450
+ #
451
+ # AN UNREADABLE SCRATCH COPY REFUSES. `decide_transition` already takes that
452
+ # line — *"refusing rather than guessing"* — and a file the parser cannot read
453
+ # is exactly the state this gate exists to keep off the plan.
454
+ phase_would_read() { # $1=scratch file $2=expected phase (lowercase) → 0 agrees, 1 refuses
455
+ local scratch="$1" want="$2" m got
456
+ m=$(bash "$script_dir/plot-plan-meta.sh" "$scratch" 2>/dev/null) || m=""
457
+ if [ -z "$m" ]; then
458
+ echo "plot-deliver: $rel — the written file does not parse, so the delivery was not landed." >&2
459
+ echo " Nothing was written. Re-run after fixing the file." >&2
460
+ return 1
461
+ fi
462
+ got=$(printf '%s' "$m" | jq -r '.phase // ""')
463
+ [ "$got" = "$want" ] && return 0
464
+
465
+ # THE REFUSAL NAMES BOTH VALUES AND THE FILE. A message saying only "delivery
466
+ # failed" throws away the half a person acts on: which phase was written, and
467
+ # which one the parser still reads. The cause is named too, because the file
468
+ # holding two records of one fact is the thing to fix — and which format ought
469
+ # to win is a decision this gate deliberately leaves to a person.
470
+ echo "plot-deliver: $rel — wrote phase '$want', but the parser still reads '$got'." >&2
471
+ echo " The plan states its phase in TWO places and they disagree: the write" >&2
472
+ echo " landed in the '## Status' block while front matter takes precedence," >&2
473
+ echo " so the delivery would have reported a success it did not achieve." >&2
474
+ echo " Nothing was written — the plan is unchanged. Remove one of the two" >&2
475
+ echo " records (front matter, or the '## Status' block) and re-run." >&2
476
+ echo " See what the parser reads: $script_dir/plot-plan-meta.sh $rel" >&2
477
+ return 1
478
+ }
479
+
422
480
  write_transition() { # $1=file $2=record $3=recorded(yes|no) → sets phase_report record_report
423
481
  local f="$1" record="$2" recorded="$3" a="$1.plot-phase" b="$1.plot-record" flipped=0
424
482
 
@@ -428,6 +486,11 @@ write_transition() { # $1=file $2=record $3=recorded(yes|no) → sets phase_repo
428
486
  [ -s "$a" ] || { rm -f "$a"; echo "plot-deliver: could not read $rel" >&2; return 1; }
429
487
 
430
488
  if [ "$recorded" = "yes" ]; then
489
+ # THE DRY RUN, on the file this arm is about to land. A refusal discards the
490
+ # scratch copy and leaves the plan byte-identical — and never reaches
491
+ # `record_state_receipt`, which would otherwise license a commit of a state
492
+ # that was refused.
493
+ phase_would_read "$a" delivered || { rm -f "$a"; return 1; }
431
494
  mv "$a" "$f" || { rm -f "$a"; return 1; }
432
495
  record_report="already"
433
496
  else
@@ -438,6 +501,9 @@ write_transition() { # $1=file $2=record $3=recorded(yes|no) → sets phase_repo
438
501
  echo " with no record is invisible to the scan. Fix the section and re-run." >&2
439
502
  return 1
440
503
  fi
504
+ # `$b` AND NOT `$a`: this arm lands the file carrying the record, so `$a`
505
+ # is content that never reaches the plan.
506
+ phase_would_read "$b" delivered || { rm -f "$a" "$b"; return 1; }
441
507
  mv "$b" "$f" || { rm -f "$a" "$b"; return 1; }
442
508
  rm -f "$a"
443
509
  record_report="written"