@plot-pm/board 0.8.0-rc.9 → 0.8.1-rc.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/dist/board-server.mjs +147 -102
- package/package.json +1 -1
- package/plot-config.sh +15 -0
- package/plot-plan-meta.sh +67 -15
package/package.json
CHANGED
package/plot-config.sh
CHANGED
|
@@ -27,6 +27,21 @@
|
|
|
27
27
|
# Project board | Branch prefixes | Plan directory | Active index |
|
|
28
28
|
# Delivered index | Sprint directory | Story directory | Story index |
|
|
29
29
|
# Plan template | Main branch | Board command
|
|
30
|
+
# Worker bound seconds a single prompt run may take in the worker loop
|
|
31
|
+
# before it is ended and the worker exits (no hop). Read by
|
|
32
|
+
# plot-worker-loop.sh; default 3600 (~1h), `0` disables it.
|
|
33
|
+
# Non-numeric or empty falls back to the default. It bounds
|
|
34
|
+
# a HUNG agent — one whose CLI crashed without exiting — so
|
|
35
|
+
# one dead worker cannot hold a slot for hours.
|
|
36
|
+
# Agent registry the directory the dispatcher writes agent manifests to,
|
|
37
|
+
# read by the board's registry. Default `.plot/agents`
|
|
38
|
+
# (repo-relative, gitignored, hence per-worktree). A board
|
|
39
|
+
# served from a worktree the dispatcher never wrote to
|
|
40
|
+
# reads an empty directory and synthesizes the whole fleet
|
|
41
|
+
# with no session ids; point this at a shared location so
|
|
42
|
+
# the board finds the registry wherever it was started.
|
|
43
|
+
# Absent = the default, so a single-checkout project is
|
|
44
|
+
# unaffected.
|
|
30
45
|
# Agent-runner keys (optional; Plot hardcodes no agent tooling, Principle 5):
|
|
31
46
|
# Worker command how /plot-dispatch runs an agent headless on a worktree.
|
|
32
47
|
# `none` = asked, and this repo starts workers by hand —
|
package/plot-plan-meta.sh
CHANGED
|
@@ -128,6 +128,18 @@
|
|
|
128
128
|
# is a note to a reviewer rather than a release note. Comment
|
|
129
129
|
# interiors stay non-content, as everywhere else in this parser,
|
|
130
130
|
# so the template's guidance block contributes nothing.
|
|
131
|
+
# long_wave_names wave names too long to be labels — a report, not a refusal.
|
|
132
|
+
# A wave name is a label (`Shaped`, `Gated`, `Offered first`);
|
|
133
|
+
# a sentence-length heading is a plan-authoring mistake the
|
|
134
|
+
# board can only render badly. Names past a threshold judgement
|
|
135
|
+
# (LONG_WAVE_NAME_MAX, set from the estate's longest legitimate
|
|
136
|
+
# name) are listed here in document order, so the reconcile
|
|
137
|
+
# sweep can surface them for a fix. Reads the SAME wave names
|
|
138
|
+
# waves[] carries — never a second scan of the file — so both
|
|
139
|
+
# the ## Branches and ## Waves spellings are covered. [] when
|
|
140
|
+
# every wave name is a label; ALWAYS present, so a consumer
|
|
141
|
+
# never reads undefined. The plan still parses in full: waves[]
|
|
142
|
+
# is unchanged and no name is shortened or dropped.
|
|
131
143
|
# issues tracker issue numbers this plan answers, from the `## Status`
|
|
132
144
|
# `Issue:` line or front matter `issue:` (sorted, unique).
|
|
133
145
|
# A DEDICATED field, never a scan of the body for `#NNN`: a
|
|
@@ -169,15 +181,18 @@
|
|
|
169
181
|
# canonical entries and the front-matter entry merge
|
|
170
182
|
# (canonical first, front matter appended)
|
|
171
183
|
# rounds how many rounds of /plot:challenge-the-plan the plan has been
|
|
172
|
-
# through, read from
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
# field
|
|
184
|
+
# through, read from `## Status` `Rounds:` first, YAML front
|
|
185
|
+
# matter `rounds:` second, and the `CHALLENGE-THE-PLAN-METADATA`
|
|
186
|
+
# block last. The field wins a disagreement (the file states
|
|
187
|
+
# what the reader sees), and the block remains readable so the
|
|
188
|
+
# 40 plans carrying only a block go on reporting correctly.
|
|
189
|
+
# **OMITTED ENTIRELY when no source carries a readable round**
|
|
190
|
+
# — absent is not zero. A plan nobody has interrogated and a
|
|
191
|
+
# plan interrogated to no effect want opposite reactions from a
|
|
192
|
+
# reader, so the field is missing rather than 0, the same rule
|
|
193
|
+
# `claimed`/`eligible` follow on the board side. A malformed or
|
|
194
|
+
# truncated block is reported the same way: the round is simply
|
|
195
|
+
# absent, and every other field still parses.
|
|
181
196
|
#
|
|
182
197
|
# title/sprint/story/assignee are the board-facing surface (`@plot-pm/board`
|
|
183
198
|
# consumes this script instead of parsing plans itself). Front matter wins over
|
|
@@ -204,7 +219,7 @@ if [ ${#files[@]} -eq 0 ] && [ ${#missing[@]} -eq 0 ]; then
|
|
|
204
219
|
fi
|
|
205
220
|
|
|
206
221
|
for f in ${missing[@]+"${missing[@]}"}; do
|
|
207
|
-
printf '{"file":"%s","format":"none","error":"file not found","phase_raw":"","phase":"NONE","phase_alt_raw":"","phase_alt":"NONE","type":"","title":"","sprint":"","story":"","assignee":"","branches":[],"prs":[],"issues":[],"malformed_prs":[],"changelog":[],"review_raw":"","review":"NONE","impl_raw":"","impl":"NONE","design_raw":"","approved_raw":"","released_raw":"","delivered_raw":"","started_raw":[]}\n' \
|
|
222
|
+
printf '{"file":"%s","format":"none","error":"file not found","phase_raw":"","phase":"NONE","phase_alt_raw":"","phase_alt":"NONE","type":"","title":"","sprint":"","story":"","assignee":"","branches":[],"prs":[],"issues":[],"malformed_prs":[],"changelog":[],"long_wave_names":[],"review_raw":"","review":"NONE","impl_raw":"","impl":"NONE","design_raw":"","approved_raw":"","released_raw":"","delivered_raw":"","started_raw":[]}\n' \
|
|
208
223
|
"$(printf '%s' "$f" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
209
224
|
done
|
|
210
225
|
|
|
@@ -278,15 +293,17 @@ function reset_state() {
|
|
|
278
293
|
fm_title = ""; fm_sprint = ""; fm_story = ""; fm_assignee = ""
|
|
279
294
|
fm_review = ""; fm_impl = ""; fm_approved = ""; fm_started = ""; fm_released = ""
|
|
280
295
|
fm_delivered = ""; fm_design = ""
|
|
296
|
+
fm_rounds = ""
|
|
281
297
|
canon_phase = ""; canon_type = ""
|
|
282
298
|
canon_sprint = ""; canon_story = ""; canon_assignee = ""
|
|
283
299
|
canon_review = ""; canon_impl = ""; canon_approved = ""; canon_released = ""
|
|
284
300
|
canon_delivered = ""; canon_design = ""
|
|
301
|
+
canon_rounds = ""
|
|
285
302
|
h1_title = ""
|
|
286
303
|
# "" means the plan carries no readable round — NOT zero. Emitted by omitting
|
|
287
304
|
# the field, so a consumer cannot mistake "never interrogated" for "asked
|
|
288
305
|
# nothing".
|
|
289
|
-
|
|
306
|
+
block_rounds = ""
|
|
290
307
|
in_fm = 0; section = ""; in_comment = 0; in_challenge = 0; in_fence = 0; branches_seen = 0; waves_seen = 0
|
|
291
308
|
delete branches; n_branches = 0
|
|
292
309
|
delete prs; n_prs = 0
|
|
@@ -399,6 +416,27 @@ function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assigne
|
|
|
399
416
|
out = out "]}"
|
|
400
417
|
}
|
|
401
418
|
out = out "]"
|
|
419
|
+
# long_wave_names[]: wave names past LONG_WAVE_NAME_MAX, in document order. A
|
|
420
|
+
# wave name is a label (`Shaped`, `Gated`, `Offered first`); a sentence-length
|
|
421
|
+
# heading is a plan-authoring mistake the board can only render badly. This
|
|
422
|
+
# REPORTS the offenders — a top-level field, never a change to waves[], so the
|
|
423
|
+
# plan still parses and every existing consumer of waves[] is untouched. The
|
|
424
|
+
# threshold is a JUDGEMENT baked into one constant (see LONG_WAVE_NAME_MAX):
|
|
425
|
+
# the longest legitimate name in the estate is `Offered first` at 13, the
|
|
426
|
+
# offender that motivated this is 53. Measured over the whole array so BOTH
|
|
427
|
+
# spellings (## Branches and ## Waves) are covered from one place. length()
|
|
428
|
+
# here is the awk byte length, which can exceed the character count for a name
|
|
429
|
+
# with a multi-byte dash — harmless: the threshold clears every real name by
|
|
430
|
+
# tens of bytes either way, so the verdict never turns on the last byte.
|
|
431
|
+
out = out ",\"long_wave_names\":["
|
|
432
|
+
lwn = 0
|
|
433
|
+
for (w = 1; w <= n_waves; w++) {
|
|
434
|
+
if (length(wave_names[w]) > LONG_WAVE_NAME_MAX) {
|
|
435
|
+
out = out (lwn > 0 ? "," : "") "\"" jesc(wave_names[w]) "\""
|
|
436
|
+
lwn++
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
out = out "]"
|
|
402
440
|
out = out ",\"review_raw\":\"" jesc(review) "\",\"review\":\"" norm_review(review) "\""
|
|
403
441
|
out = out ",\"impl_raw\":\"" jesc(impl) "\",\"impl\":\"" norm_impl(impl) "\""
|
|
404
442
|
out = out ",\"design_raw\":\"" jesc(design) "\""
|
|
@@ -408,12 +446,24 @@ function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assigne
|
|
|
408
446
|
out = out ",\"started_raw\":["
|
|
409
447
|
for (i = 1; i <= n_started; i++) out = out (i > 1 ? "," : "") "\"" jesc(started[i]) "\""
|
|
410
448
|
out = out "]"
|
|
411
|
-
#
|
|
449
|
+
# Rounds: preference order: ## Status, front matter, CHALLENGE block fallback.
|
|
450
|
+
# A placeholder ("<!-- optional -->") counts as absent, like Sprint/Story.
|
|
451
|
+
# OMITTED, not zeroed, when no source carries a readable round.
|
|
452
|
+
rounds = ""
|
|
453
|
+
_r = strip_placeholder(canon_rounds)
|
|
454
|
+
if (_r == "") _r = strip_placeholder(fm_rounds)
|
|
455
|
+
if (_r == "") _r = block_rounds
|
|
456
|
+
if (_r != "" && _r ~ /^[0-9]+$/) rounds = _r + 0
|
|
412
457
|
if (rounds != "") out = out ",\"rounds\":" rounds
|
|
413
458
|
out = out "}"
|
|
414
459
|
print out
|
|
415
460
|
}
|
|
416
|
-
|
|
461
|
+
# The longest wave name that still reads as a label, not prose. A JUDGEMENT, not
|
|
462
|
+
# a measurement: the longest legitimate name in the estate is `Offered first`
|
|
463
|
+
# (13), and the offender this exists to catch is a 53-character sentence, so the
|
|
464
|
+
# line sits well clear of both. Reported, never enforced — a name past it makes
|
|
465
|
+
# `long_wave_names`, and nothing refuses the plan.
|
|
466
|
+
BEGIN { branch_re = "`(" PREFIXES ")/[^`]+`"; LONG_WAVE_NAME_MAX = 40 }
|
|
417
467
|
FNR == 1 {
|
|
418
468
|
if (NR > 1) emit_record()
|
|
419
469
|
reset_state()
|
|
@@ -442,6 +492,7 @@ in_fm {
|
|
|
442
492
|
# here: front matter in this repo is a flat key/value surface, and guessing a
|
|
443
493
|
# list grammar the format never promised would invent a contract.
|
|
444
494
|
else if (lower ~ /^changelog:/ && fm_changelog == "") fm_changelog = val_after_colon($0)
|
|
495
|
+
else if (lower ~ /^rounds:/ && fm_rounds == "") fm_rounds = val_after_colon($0)
|
|
445
496
|
next
|
|
446
497
|
}
|
|
447
498
|
# Interior of multi-line HTML comments is non-content (template guidance
|
|
@@ -454,11 +505,11 @@ in_fm {
|
|
|
454
505
|
# block (question history, category coverage) stays non-content — the script
|
|
455
506
|
# collects, the skill interprets.
|
|
456
507
|
in_comment {
|
|
457
|
-
if (in_challenge &&
|
|
508
|
+
if (in_challenge && block_rounds == "" && $0 ~ /^[ \t]*"round"[ \t]*:[ \t]*[0-9]+/) {
|
|
458
509
|
_r = $0
|
|
459
510
|
sub(/^[ \t]*"round"[ \t]*:[ \t]*/, "", _r)
|
|
460
511
|
sub(/[^0-9].*$/, "", _r)
|
|
461
|
-
if (_r != "")
|
|
512
|
+
if (_r != "") block_rounds = _r + 0
|
|
462
513
|
}
|
|
463
514
|
if ($0 ~ /-->/) { in_comment = 0; in_challenge = 0 }
|
|
464
515
|
next
|
|
@@ -518,6 +569,7 @@ section == "status" {
|
|
|
518
569
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**review[:*]/ && canon_review == "") canon_review = val_after_colon($0)
|
|
519
570
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**impl[:*]/ && canon_impl == "") canon_impl = val_after_colon($0)
|
|
520
571
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**design[:*]/ && canon_design == "") canon_design = val_after_colon($0)
|
|
572
|
+
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**rounds[:*]/ && canon_rounds == "") canon_rounds = val_after_colon($0)
|
|
521
573
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**approved[:*]/ && canon_approved == "") canon_approved = val_after_colon($0)
|
|
522
574
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**released[:*]/ && canon_released == "") canon_released = val_after_colon($0)
|
|
523
575
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**delivered[:*]/ && canon_delivered == "") canon_delivered = val_after_colon($0)
|