@plot-pm/board 0.2.1 → 0.3.0-rc.1
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 +6 -6
- package/package.json +1 -1
- package/plot-config.sh +26 -7
- package/plot-plan-meta.sh +73 -3
package/package.json
CHANGED
package/plot-config.sh
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
# back to the default so callers can rely on the output unconditionally.
|
|
7
7
|
# Designed for small-model consumption: one value on stdout, no interpretation.
|
|
8
8
|
#
|
|
9
|
-
# This is the ONE place that knows where plot configuration lives (
|
|
10
|
-
# `## Plot Config` section in the repo-root CLAUDE.md).
|
|
11
|
-
#
|
|
12
|
-
#
|
|
9
|
+
# This is the ONE place that knows where plot configuration lives (a
|
|
10
|
+
# `## Plot Config` section in the repo-root CLAUDE.md or AGENTS.md).
|
|
11
|
+
# CLAUDE.md is checked first for backwards compatibility; AGENTS.md is the
|
|
12
|
+
# fallback for repos that have migrated to a hub-and-spoke agent-rules layout.
|
|
13
|
+
# Helpers must call this instead of grepping either file themselves, so the
|
|
14
|
+
# storage location/format can evolve without touching every consumer.
|
|
13
15
|
#
|
|
14
16
|
# Grammar accepted inside the section (case-insensitive key, bold optional):
|
|
15
17
|
# - **Plan directory:** docs/plans/
|
|
@@ -23,7 +25,15 @@
|
|
|
23
25
|
#
|
|
24
26
|
# Known keys (see the plot skill's Setup section):
|
|
25
27
|
# Project board | Branch prefixes | Plan directory | Active index |
|
|
26
|
-
# Delivered index | Sprint directory |
|
|
28
|
+
# Delivered index | Sprint directory | Story directory | Story index |
|
|
29
|
+
# Plan template | Main branch
|
|
30
|
+
# Plot 2 posture keys (repo-declared ceremony bounds; all optional):
|
|
31
|
+
# Plan PRs required | never | optional (never = hard gate)
|
|
32
|
+
# Implementation home this repo | <repo/path list> | none
|
|
33
|
+
# Hosts plans yes | no (no = refuse plan files)
|
|
34
|
+
# Tracker plot | jira | github-issues | linear (+ URL)
|
|
35
|
+
# (plot = plans in this repo ARE the tracker; absent = same)
|
|
36
|
+
# Git host github | bitbucket (resolves gh vs bb)
|
|
27
37
|
#
|
|
28
38
|
# `Plan template` is a repo-root-relative path to the plan template /plot-idea
|
|
29
39
|
# instantiates; when absent, /plot-idea falls back to the shipped template.
|
|
@@ -40,10 +50,19 @@ if [ "$cmd" != "get" ]; then
|
|
|
40
50
|
fi
|
|
41
51
|
|
|
42
52
|
root=$(git rev-parse --show-toplevel 2>/dev/null) || root="."
|
|
43
|
-
|
|
53
|
+
|
|
54
|
+
# Find the first repo-root file that contains a ## Plot Config section.
|
|
55
|
+
# CLAUDE.md wins for backwards compatibility; AGENTS.md is the modern fallback.
|
|
56
|
+
config_file=""
|
|
57
|
+
for _candidate in "$root/CLAUDE.md" "$root/AGENTS.md"; do
|
|
58
|
+
if [ -f "$_candidate" ] && grep -qi "^##[[:space:]]*plot config" "$_candidate" 2>/dev/null; then
|
|
59
|
+
config_file="$_candidate"
|
|
60
|
+
break
|
|
61
|
+
fi
|
|
62
|
+
done
|
|
44
63
|
|
|
45
64
|
value=""
|
|
46
|
-
if [ -
|
|
65
|
+
if [ -n "$config_file" ]; then
|
|
47
66
|
# Extract the `## Plot Config` section (case-insensitive, portable awk).
|
|
48
67
|
section=$(awk '
|
|
49
68
|
/^##[[:space:]]/ { in_section = (tolower($0) ~ /^##[[:space:]]+plot config[[:space:]]*$/) ; next }
|
package/plot-plan-meta.sh
CHANGED
|
@@ -64,6 +64,23 @@
|
|
|
64
64
|
# quoted, matching the known prefixes; sorted, unique)
|
|
65
65
|
# prs PR numbers from `→ #NNN` links in the `## Branches`
|
|
66
66
|
# section (sorted, unique)
|
|
67
|
+
# review_raw the plan's review-channel answer as written (`## Status`
|
|
68
|
+
# `Review:` or front matter `review:`); "" if absent
|
|
69
|
+
# review normalized: pr|in-session|ballot|UNKNOWN|NONE
|
|
70
|
+
# impl_raw the plan's implementation-home answer as written
|
|
71
|
+
# (`## Status` `Impl:` or front matter `impl:`); "" if absent
|
|
72
|
+
# impl normalized: own-branches|same-branch|other-repo|none|
|
|
73
|
+
# UNKNOWN|NONE ("nowhere" is accepted for none; free-text
|
|
74
|
+
# like "here, own branches" normalizes by token)
|
|
75
|
+
# approved_raw the approval transition record as written (`## Status`
|
|
76
|
+
# `Approved:` or front matter `approved:` — who/when/channel,
|
|
77
|
+
# e.g. "2026-07-30, alice, in-session"); "" if absent
|
|
78
|
+
# started_raw implementation-start records, one raw string per
|
|
79
|
+
# `Started:` line in `## Status` (repeatable; front matter
|
|
80
|
+
# `started:` contributes one entry); [] if absent.
|
|
81
|
+
# Exception to "front matter wins": started is ADDITIVE —
|
|
82
|
+
# canonical entries and the front-matter entry merge
|
|
83
|
+
# (canonical first, front matter appended)
|
|
67
84
|
#
|
|
68
85
|
# title/sprint/story/assignee are the board-facing surface (`@plot-pm/board`
|
|
69
86
|
# consumes this script instead of parsing plans itself). Front matter wins over
|
|
@@ -90,7 +107,7 @@ if [ ${#files[@]} -eq 0 ] && [ ${#missing[@]} -eq 0 ]; then
|
|
|
90
107
|
fi
|
|
91
108
|
|
|
92
109
|
for f in ${missing[@]+"${missing[@]}"}; do
|
|
93
|
-
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":[]}\n' \
|
|
110
|
+
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":[],"review_raw":"","review":"NONE","impl_raw":"","impl":"NONE","approved_raw":"","started_raw":[]}\n' \
|
|
94
111
|
"$(printf '%s' "$f" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
|
95
112
|
done
|
|
96
113
|
|
|
@@ -134,17 +151,45 @@ function norm_type(raw, lower, toks, n, i, t) {
|
|
|
134
151
|
}
|
|
135
152
|
return ""
|
|
136
153
|
}
|
|
154
|
+
# Canonicalize a ceremony answer: lowercase, punctuation/space runs → "-",
|
|
155
|
+
# so "here, own branches" → "here-own-branches" and token checks are uniform.
|
|
156
|
+
function canon_answer(raw, s) {
|
|
157
|
+
s = tolower(raw)
|
|
158
|
+
gsub(/[^a-z0-9]+/, "-", s)
|
|
159
|
+
sub(/^-+/, "", s); sub(/-+$/, "", s)
|
|
160
|
+
return s
|
|
161
|
+
}
|
|
162
|
+
function norm_review(raw, s) {
|
|
163
|
+
if (raw == "") return "NONE"
|
|
164
|
+
s = "-" canon_answer(raw) "-"
|
|
165
|
+
if (s ~ /-in-session-/) return "in-session"
|
|
166
|
+
if (s ~ /-ballot-/) return "ballot"
|
|
167
|
+
if (s ~ /-pr-/) return "pr"
|
|
168
|
+
return "UNKNOWN"
|
|
169
|
+
}
|
|
170
|
+
function norm_impl(raw, s) {
|
|
171
|
+
if (raw == "") return "NONE"
|
|
172
|
+
s = "-" canon_answer(raw) "-"
|
|
173
|
+
if (s ~ /-own-branches-/) return "own-branches"
|
|
174
|
+
if (s ~ /-same-branch-/) return "same-branch"
|
|
175
|
+
if (s ~ /-other-repo-/) return "other-repo"
|
|
176
|
+
if (s ~ /-none-/ || s ~ /-nowhere-/) return "none"
|
|
177
|
+
return "UNKNOWN"
|
|
178
|
+
}
|
|
137
179
|
function reset_state() {
|
|
138
180
|
fm_status = ""; fm_phase = ""; fm_type = ""
|
|
139
181
|
fm_title = ""; fm_sprint = ""; fm_story = ""; fm_assignee = ""
|
|
182
|
+
fm_review = ""; fm_impl = ""; fm_approved = ""; fm_started = ""
|
|
140
183
|
canon_phase = ""; canon_type = ""
|
|
141
184
|
canon_sprint = ""; canon_story = ""; canon_assignee = ""
|
|
185
|
+
canon_review = ""; canon_impl = ""; canon_approved = ""
|
|
142
186
|
h1_title = ""
|
|
143
|
-
in_fm = 0; section = ""
|
|
187
|
+
in_fm = 0; section = ""; in_comment = 0
|
|
144
188
|
delete branches; n_branches = 0
|
|
145
189
|
delete prs; n_prs = 0
|
|
190
|
+
delete started; n_started = 0
|
|
146
191
|
}
|
|
147
|
-
function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assignee, i, j, out, sorted_b, sorted_p, nb, np) {
|
|
192
|
+
function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assignee, review, impl, approved, i, j, out, sorted_b, sorted_p, nb, np) {
|
|
148
193
|
if (fm_status != "" || fm_phase != "") {
|
|
149
194
|
fmt = "frontmatter"
|
|
150
195
|
praw = (fm_status != "") ? fm_status : fm_phase
|
|
@@ -161,6 +206,10 @@ function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assigne
|
|
|
161
206
|
sprint = strip_placeholder((fm_sprint != "") ? fm_sprint : canon_sprint)
|
|
162
207
|
story = strip_placeholder((fm_story != "") ? fm_story : canon_story)
|
|
163
208
|
assignee = strip_placeholder((fm_assignee != "") ? fm_assignee : canon_assignee)
|
|
209
|
+
review = strip_placeholder((fm_review != "") ? fm_review : canon_review)
|
|
210
|
+
impl = strip_placeholder((fm_impl != "") ? fm_impl : canon_impl)
|
|
211
|
+
approved = strip_placeholder((fm_approved != "") ? fm_approved : canon_approved)
|
|
212
|
+
if (fm_started != "" && strip_placeholder(fm_started) != "") started[++n_started] = fm_started
|
|
164
213
|
# Insertion sort + dedupe (portable: no gawk asort).
|
|
165
214
|
nb = 0
|
|
166
215
|
for (i = 1; i <= n_branches; i++) {
|
|
@@ -186,6 +235,12 @@ function emit_record( fmt, praw, palt_raw, traw, title, sprint, story, assigne
|
|
|
186
235
|
for (i = 1; i <= nb; i++) out = out (i > 1 ? "," : "") "\"" jesc(sorted_b[i]) "\""
|
|
187
236
|
out = out "],\"prs\":["
|
|
188
237
|
for (i = 1; i <= np; i++) out = out (i > 1 ? "," : "") sorted_p[i]
|
|
238
|
+
out = out "]"
|
|
239
|
+
out = out ",\"review_raw\":\"" jesc(review) "\",\"review\":\"" norm_review(review) "\""
|
|
240
|
+
out = out ",\"impl_raw\":\"" jesc(impl) "\",\"impl\":\"" norm_impl(impl) "\""
|
|
241
|
+
out = out ",\"approved_raw\":\"" jesc(approved) "\""
|
|
242
|
+
out = out ",\"started_raw\":["
|
|
243
|
+
for (i = 1; i <= n_started; i++) out = out (i > 1 ? "," : "") "\"" jesc(started[i]) "\""
|
|
189
244
|
out = out "]}"
|
|
190
245
|
print out
|
|
191
246
|
}
|
|
@@ -206,8 +261,16 @@ in_fm {
|
|
|
206
261
|
else if (lower ~ /^sprint:/ && fm_sprint == "") fm_sprint = val_after_colon($0)
|
|
207
262
|
else if (lower ~ /^story:/ && fm_story == "") fm_story = val_after_colon($0)
|
|
208
263
|
else if (lower ~ /^assignee:/ && fm_assignee == "") fm_assignee = val_after_colon($0)
|
|
264
|
+
else if (lower ~ /^review:/ && fm_review == "") fm_review = val_after_colon($0)
|
|
265
|
+
else if (lower ~ /^impl:/ && fm_impl == "") fm_impl = val_after_colon($0)
|
|
266
|
+
else if (lower ~ /^approved:/ && fm_approved == "") fm_approved = val_after_colon($0)
|
|
267
|
+
else if (lower ~ /^started:/ && fm_started == "") fm_started = val_after_colon($0)
|
|
209
268
|
next
|
|
210
269
|
}
|
|
270
|
+
# Interior of multi-line HTML comments is non-content (template guidance
|
|
271
|
+
# blocks); single-line "<!-- ... -->" placeholders are unaffected.
|
|
272
|
+
in_comment { if ($0 ~ /-->/) in_comment = 0; next }
|
|
273
|
+
/<!--/ && $0 !~ /-->/ { in_comment = 1; next }
|
|
211
274
|
# First H1 is the title fallback (front matter title: still wins in emit).
|
|
212
275
|
/^#[ \t]/ && h1_title == "" { h1_title = trim(substr($0, 2)) }
|
|
213
276
|
/^## / {
|
|
@@ -223,6 +286,13 @@ section == "status" {
|
|
|
223
286
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**type[:*]/ && canon_type == "") canon_type = val_after_colon($0)
|
|
224
287
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**sprint[:*]/ && canon_sprint == "") canon_sprint = val_after_colon($0)
|
|
225
288
|
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**story[:*]/ && canon_story == "") canon_story = val_after_colon($0)
|
|
289
|
+
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**review[:*]/ && canon_review == "") canon_review = val_after_colon($0)
|
|
290
|
+
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**impl[:*]/ && canon_impl == "") canon_impl = val_after_colon($0)
|
|
291
|
+
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**approved[:*]/ && canon_approved == "") canon_approved = val_after_colon($0)
|
|
292
|
+
else if (lower ~ /^[ \t]*[-*]?[ \t]*\**started[:*]/) {
|
|
293
|
+
_s = strip_placeholder(val_after_colon($0))
|
|
294
|
+
if (_s != "") started[++n_started] = _s
|
|
295
|
+
}
|
|
226
296
|
next
|
|
227
297
|
}
|
|
228
298
|
section == "approval" {
|