@plot-pm/board 0.7.0-rc.2 → 0.7.0-rc.21
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/dist/board-server.mjs +81 -81
- package/package.json +1 -1
- package/plot-plan-meta.sh +135 -7
package/package.json
CHANGED
package/plot-plan-meta.sh
CHANGED
|
@@ -36,6 +36,27 @@
|
|
|
36
36
|
# Front matter wins when both are present (it is the machine-facing surface).
|
|
37
37
|
# A file with neither is reported as format "none" (pre-plot / legacy plan).
|
|
38
38
|
#
|
|
39
|
+
# The IMPLEMENTATION section (which branches, in which waves, with which PRs)
|
|
40
|
+
# has TWO spellings, and this parser reads both:
|
|
41
|
+
#
|
|
42
|
+
# ## Branches (old) the branch rides the list line, meta mixed with prose:
|
|
43
|
+
# ### Removed
|
|
44
|
+
# - `bug/foo` — loses its half → #300
|
|
45
|
+
#
|
|
46
|
+
# ## Waves (new) the `### ` heading carries the meta, the line is prose:
|
|
47
|
+
# ### Removed (Branch: bug/foo, PR: #300)
|
|
48
|
+
# - loses its half
|
|
49
|
+
#
|
|
50
|
+
# Both emit the SAME branches/prs/waves arrays. The new shape is the format Plot
|
|
51
|
+
# writes and documents; the old one is kept readable because a format change owes
|
|
52
|
+
# its estate a migration that moves files one at a time, and a plan moved one
|
|
53
|
+
# commit before the parser learns the shape must not read as silently empty —
|
|
54
|
+
# the failure that makes a plan disappear from the fleet scan and pass the
|
|
55
|
+
# delivery gate. In the new shape the branch comes from the HEADING, so a
|
|
56
|
+
# backticked name in a description cannot be mistaken for a branch — the defect
|
|
57
|
+
# the old shape invited (a second path-shaped token on a line read as a phantom
|
|
58
|
+
# branch) is structurally impossible.
|
|
59
|
+
#
|
|
39
60
|
# Phase values are normalized by scanning whitespace-separated tokens for the
|
|
40
61
|
# first known phase word — so decorated real-world values like
|
|
41
62
|
# "Delivered (2026-06-29) — split done" normalize to "delivered". A non-empty
|
|
@@ -66,8 +87,13 @@
|
|
|
66
87
|
# front matter `story:`); "" if absent or a placeholder
|
|
67
88
|
# assignee github handle from the `## Approval` `Assignee:` line or
|
|
68
89
|
# front matter `assignee:`; "" if absent
|
|
69
|
-
# branches branch names
|
|
70
|
-
#
|
|
90
|
+
# branches branch names, sorted and unique, read from EITHER spelling:
|
|
91
|
+
# the old `## Branches` section (backtick-quoted in the list
|
|
92
|
+
# line, matching the known prefixes) OR the new `## Waves`
|
|
93
|
+
# section (`Branch:` in a `### ` heading — see below). Both
|
|
94
|
+
# spellings emit the same array; a plan carries one or the
|
|
95
|
+
# other, and the parser reads both so a migration that moves
|
|
96
|
+
# files one at a time never makes a plan silently empty.
|
|
71
97
|
# NOTE: per-branch annotations (`<!-- deferred: ... -->`,
|
|
72
98
|
# `<!-- claimed: ... -->`) bind to the LINE carrying the
|
|
73
99
|
# backticked branch name. An annotation on a wrapped
|
|
@@ -75,10 +101,13 @@
|
|
|
75
101
|
# `<!-- deferred -->` (bare, no colon) sets the flag with no
|
|
76
102
|
# reason; `waves[].branches[].deferred_reason` carries the
|
|
77
103
|
# sentence after the colon, "" where none was written.
|
|
78
|
-
# prs PR numbers
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
104
|
+
# prs PR numbers, sorted and unique, read from EITHER spelling:
|
|
105
|
+
# `→ #NNN` / `→ owner/repo#NNN` links in the `## Branches`
|
|
106
|
+
# section, OR `PR: #NNN` in a `## Waves` `### ` heading. The
|
|
107
|
+
# repo part is matched but not retained: callers ask which PRs
|
|
108
|
+
# are a plan's evidence, and plot-host.sh resolves where each
|
|
109
|
+
# one lives. An absent PR contributes nothing — not "", not 0 —
|
|
110
|
+
# the same rule `Issue:` follows.
|
|
82
111
|
# malformed_prs near-miss annotations, verbatim — currently `→#NNN` with no
|
|
83
112
|
# space. Reported rather than dropped: "no annotation" is a
|
|
84
113
|
# claim the sweep acts on, so a typo that reads as absence
|
|
@@ -258,7 +287,7 @@ function reset_state() {
|
|
|
258
287
|
# the field, so a consumer cannot mistake "never interrogated" for "asked
|
|
259
288
|
# nothing".
|
|
260
289
|
rounds = ""
|
|
261
|
-
in_fm = 0; section = ""; in_comment = 0; in_challenge = 0; branches_seen = 0
|
|
290
|
+
in_fm = 0; section = ""; in_comment = 0; in_challenge = 0; in_fence = 0; branches_seen = 0; waves_seen = 0
|
|
262
291
|
delete branches; n_branches = 0
|
|
263
292
|
delete prs; n_prs = 0
|
|
264
293
|
delete malformed_prs; n_malformed_prs = 0
|
|
@@ -442,6 +471,22 @@ in_comment {
|
|
|
442
471
|
in_challenge = ($0 ~ /CHALLENGE-THE-PLAN-METADATA/) ? 1 : 0
|
|
443
472
|
next
|
|
444
473
|
}
|
|
474
|
+
# A fenced code block is illustration, never contract — the same standing rule
|
|
475
|
+
# comment interiors and repeated headings already follow. A plan that documents
|
|
476
|
+
# the plan format shows a `## Waves` or `## Branches` block inside a ``` fence,
|
|
477
|
+
# and those example headings must contribute no section, no branch and no PR.
|
|
478
|
+
#
|
|
479
|
+
# Measured need: waves-name-themselves shows its `## Waves` example in a fenced
|
|
480
|
+
# block whose `### Removed (Branch: bug/an-agent…)` headings are realistic. With
|
|
481
|
+
# no fence tracking the parser read them as real branches of the plan — and the
|
|
482
|
+
# same trick already fooled the OLD `## Branches` path (a fenced `## Branches`
|
|
483
|
+
# example won its first-heading-wins guard and hid the real section). Toggling on
|
|
484
|
+
# a fence fence-marker line closes both.
|
|
485
|
+
#
|
|
486
|
+
# A fence marker is a line whose first non-space run is ``` or ~~~ (an info
|
|
487
|
+
# string like ```markdown may follow). The marker line itself is never content.
|
|
488
|
+
/^[ \t]*(```|~~~)/ { in_fence = !in_fence; next }
|
|
489
|
+
in_fence { next }
|
|
445
490
|
# First H1 is the title fallback (front matter title: still wins in emit).
|
|
446
491
|
/^#[ \t]/ && h1_title == "" { h1_title = trim(substr($0, 2)) }
|
|
447
492
|
/^## / {
|
|
@@ -449,6 +494,12 @@ in_comment {
|
|
|
449
494
|
# First `## Branches` wins: a plan documenting the plan format quotes the
|
|
450
495
|
# section in prose, and those later headings are illustration, not contract.
|
|
451
496
|
else if ($0 ~ /^## Branches/) { section = branches_seen ? "" : "branches"; branches_seen = 1 }
|
|
497
|
+
# `## Waves` is the new spelling: the branch and PR live in the `### ` heading,
|
|
498
|
+
# the line below is prose. First one wins, for the same reason `## Branches`
|
|
499
|
+
# does. A plan carries one or the other — but the parser reads both while the
|
|
500
|
+
# migration moves 85 files, so a file moved one commit early never reads
|
|
501
|
+
# as silently empty.
|
|
502
|
+
else if ($0 ~ /^## Waves/) { section = waves_seen ? "" : "waves"; waves_seen = 1 }
|
|
452
503
|
else if ($0 ~ /^## Approval/) section = "approval"
|
|
453
504
|
# First `## Changelog` wins, for the same reason `## Branches` does: a plan
|
|
454
505
|
# about the plan format quotes the section in prose, and the later heading is
|
|
@@ -613,5 +664,82 @@ section == "branches" {
|
|
|
613
664
|
}
|
|
614
665
|
next
|
|
615
666
|
}
|
|
667
|
+
# `## Waves` — the new spelling. The `### ` heading carries the meta (which
|
|
668
|
+
# branch, which PR) and the line below is prose. This is the inverse of
|
|
669
|
+
# `## Branches`, where the branch rides the list line: here the branch comes
|
|
670
|
+
# from the HEADING, so a backticked name in a description cannot be mistaken for
|
|
671
|
+
# a branch — the defect the old shape invited is structurally impossible.
|
|
672
|
+
#
|
|
673
|
+
# The emitted arrays (branches, prs, waves) must be byte-identical to what the
|
|
674
|
+
# old shape produces for the same plan. So this shares every accumulation
|
|
675
|
+
# variable with the branches handler above; only the EXTRACTION differs.
|
|
676
|
+
section == "waves" {
|
|
677
|
+
# Only the `### ` heading carries meta. Body lines are prose — never scanned
|
|
678
|
+
# for a branch or a PR, which is the whole point of the new shape.
|
|
679
|
+
if ($0 !~ /^###[ \t]/) next
|
|
680
|
+
|
|
681
|
+
# A heading opens a wave whose NAME is the heading text with the
|
|
682
|
+
# `(Branch: …, PR: …)` parenthetical and any trailing annotation stripped, so
|
|
683
|
+
# `### Removed (Branch: bug/foo, PR: #300)` names the wave `Removed` — exactly
|
|
684
|
+
# what the old shape (bare `### Removed`) produced, which is what keeps the two
|
|
685
|
+
# spellings byte-identical.
|
|
686
|
+
wname = trim(substr($0, 4))
|
|
687
|
+
# Drop a trailing HTML comment (claimed/deferred ride the heading line now).
|
|
688
|
+
sub(/[ \t]*<!--.*$/, "", wname)
|
|
689
|
+
# Drop the `(Branch: …)` meta parenthetical. Anchored to `(Branch:` so a
|
|
690
|
+
# parenthetical in a genuine wave name — none exist, but the grammar must not
|
|
691
|
+
# assume it — is not eaten unless it is the meta block.
|
|
692
|
+
sub(/[ \t]*\(Branch:.*$/, "", wname)
|
|
693
|
+
wname = trim(wname)
|
|
694
|
+
wave_names[++n_waves] = wname
|
|
695
|
+
|
|
696
|
+
# Claim/deferral annotations bind to the line carrying the branch name, which
|
|
697
|
+
# is the heading. Read before any match() below, which clobbers RSTART/RLENGTH.
|
|
698
|
+
claim_note = ""
|
|
699
|
+
if (index($0, "claimed:") > 0) {
|
|
700
|
+
_c = $0
|
|
701
|
+
sub(/^.*<!--[ \t]*claimed:[ \t]*/, "", _c)
|
|
702
|
+
sub(/[ \t]*-->.*$/, "", _c)
|
|
703
|
+
claim_note = trim(_c)
|
|
704
|
+
}
|
|
705
|
+
defer_note = ""
|
|
706
|
+
if (index($0, "deferred:") > 0) {
|
|
707
|
+
_d = $0
|
|
708
|
+
sub(/^.*<!--[ \t]*deferred:[ \t]*/, "", _d)
|
|
709
|
+
sub(/[ \t]*-->.*$/, "", _d)
|
|
710
|
+
defer_note = trim(_d)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
# The branch is the `Branch:` value, matched against the known prefixes exactly
|
|
714
|
+
# as the old shape matched the backticked name. Written unquoted in the heading
|
|
715
|
+
# (`Branch: bug/foo`), so the match is on a bare token, not on backticks. A
|
|
716
|
+
# heading with no readable branch still opened a wave above — so a `## Waves`
|
|
717
|
+
# section is never silently empty, which is the failure this plan refuses: a
|
|
718
|
+
# consumer sees a wave it could not extract a branch from, not an absence.
|
|
719
|
+
hmeta = $0
|
|
720
|
+
if (match(hmeta, "Branch:[ \t]*(" PREFIXES ")/[^ \t,)]+")) {
|
|
721
|
+
b = substr(hmeta, RSTART, RLENGTH)
|
|
722
|
+
sub(/^Branch:[ \t]*/, "", b)
|
|
723
|
+
branches[++n_branches] = b
|
|
724
|
+
wave_of[n_branches] = n_waves
|
|
725
|
+
wave_seq[n_branches] = ++wave_count[n_waves]
|
|
726
|
+
# Bare `<!-- deferred -->` sets the flag with no reason, same as the old
|
|
727
|
+
# shape; `deferred:` carries the reason.
|
|
728
|
+
deferred_of[n_branches] = ($0 ~ /<!--[ \t]*deferred[ \t]*(:|-->)/) ? "true" : "false"
|
|
729
|
+
deferred_why[n_branches] = defer_note
|
|
730
|
+
claimed_of[n_branches] = claim_note
|
|
731
|
+
ordered_b[n_branches] = b
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
# The PR is the `PR: #NNN` value in the heading. Absent PR: contributes
|
|
735
|
+
# nothing — not "", not 0 — the same rule `Issue:` follows. Only a heading
|
|
736
|
+
# that carries the field adds to prs.
|
|
737
|
+
if (match($0, /PR:[ \t]*#[0-9]+/)) {
|
|
738
|
+
p = substr($0, RSTART, RLENGTH)
|
|
739
|
+
sub(/^.*#/, "", p)
|
|
740
|
+
prs[++n_prs] = p
|
|
741
|
+
}
|
|
742
|
+
next
|
|
743
|
+
}
|
|
616
744
|
END { if (NR > 0) emit_record() }
|
|
617
745
|
' "${files[@]}"
|