@pmelab/gtd 8.1.0 → 8.2.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/README.md +95 -7
- package/bin/gtd +101 -16
- package/dist/gtd.bundle.mjs +30 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,20 +285,106 @@ re-launching it — its opening move captures whatever you left, so you never ru
|
|
|
285
285
|
|
|
286
286
|
Anything richer at that boundary — opening your editor, desktop notifications,
|
|
287
287
|
terminal-multiplexer status — is the job of an outer wrapper around `gtd`, not
|
|
288
|
-
of the loop itself
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
of the loop itself; see
|
|
289
|
+
[Terminal-multiplexer status: a herdr wrapper](#terminal-multiplexer-status-a-herdr-wrapper)
|
|
290
|
+
below for a worked example. Pass `--once` to restrict a run to exactly one beat
|
|
291
|
+
— one human-gate capture, one check, or one agent turn — instead of driving all
|
|
292
|
+
the way to idle.
|
|
291
293
|
|
|
292
294
|
Bare `gtd` prints one line per event — colored and emoji on a real terminal,
|
|
293
295
|
plain ASCII under `NO_COLOR` or when piped — and redirects the noisier
|
|
294
296
|
agent/check/step subprocess output to a per-repo/per-worktree log file (its path
|
|
295
|
-
is the run's first output line, ready to `tail -f`).
|
|
297
|
+
is the run's first output line, ready to `tail -f`). Any execution that FAILS
|
|
298
|
+
also replays its last 20 lines of output inline on stderr, on top of the log
|
|
299
|
+
still holding the complete record: an agent turn that fails stops the loop (it
|
|
300
|
+
would fail identically next lap), while a check script that exits non-zero is
|
|
301
|
+
reported as a warning and the loop carries on, because the outcome lives in the
|
|
302
|
+
tree, not in the script's exit code.
|
|
296
303
|
|
|
297
304
|
A workflow state can declare an optional `label:` — a human-readable display
|
|
298
305
|
name surfaced in `gtd next --json`/`gtd status`. The driver uses it for its
|
|
299
306
|
per-beat progress lines; an outer wrapper (a terminal multiplexer, a notifier)
|
|
300
307
|
can use it the same way.
|
|
301
308
|
|
|
309
|
+
### Terminal-multiplexer status: a herdr wrapper
|
|
310
|
+
|
|
311
|
+
[herdr](https://herdr.dev) shows a per-pane status in its sidebar. This wrapper
|
|
312
|
+
reports `gtd`'s own lifecycle to that status without any herdr-specific
|
|
313
|
+
knowledge in `gtd` itself: `working` while the loop drives, `blocked` when it
|
|
314
|
+
comes to rest on a human (a gate, a stall, a non-zero exit), and `idle`
|
|
315
|
+
(rendered as "done" once the pane's tab is unfocused) when a run ends without
|
|
316
|
+
anything owed. It needs nothing from `gtd` beyond `gtd next --json`'s `.actor`
|
|
317
|
+
field, which the loop's driver already reads at every beat.
|
|
318
|
+
|
|
319
|
+
Save this as `~/.local/bin/gtdh`, `chmod +x` it, and run `gtdh` in place of
|
|
320
|
+
`gtd` — it's a plain bash file, so it works from fish or any other shell, and it
|
|
321
|
+
forwards every argument (`gtdh --once`, `gtdh status`, ...).
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
#!/usr/bin/env bash
|
|
325
|
+
# Report gtd's own lifecycle to the herdr pane it runs in: working while the
|
|
326
|
+
# loop drives, blocked when it rests on you, idle (shown as "done") otherwise.
|
|
327
|
+
# Needs nothing from gtd but `gtd next --json`. Outside herdr it is a plain
|
|
328
|
+
# passthrough. Requires `jq` — so does gtd's own loop driver.
|
|
329
|
+
set -uo pipefail
|
|
330
|
+
|
|
331
|
+
pane="${HERDR_PANE_ID:-}"
|
|
332
|
+
|
|
333
|
+
# `--agent gtd` is deliberate: herdr only lets screen detection (or a visible
|
|
334
|
+
# approval prompt) override a reported state when the reported label names a
|
|
335
|
+
# KNOWN agent. "gtd" names none, so the loop's `claude -p` turns can never move
|
|
336
|
+
# this pane's status.
|
|
337
|
+
report() { # report <state>
|
|
338
|
+
[ -n "$pane" ] || return 0
|
|
339
|
+
herdr pane report-agent "$pane" \
|
|
340
|
+
--source custom:gtd --agent gtd --state "$1" >/dev/null 2>&1 || true
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
report working
|
|
344
|
+
trap 'report blocked; exit 130' INT TERM
|
|
345
|
+
|
|
346
|
+
# HERDR_PANE_ID is unset for the loop: herdr's Claude Code hook needs it, and
|
|
347
|
+
# without it no `claude -p` turn reports a claude SESSION identity for this
|
|
348
|
+
# pane. That is load-bearing, not cosmetic — a pane that owns one rejects every
|
|
349
|
+
# later state report, so the report below would be dropped.
|
|
350
|
+
env -u HERDR_PANE_ID gtd "$@"
|
|
351
|
+
rc=$?
|
|
352
|
+
|
|
353
|
+
# Whose turn is it now? `gtd next --json` is mutation-free. A human actor means
|
|
354
|
+
# gtd is waiting on you; anything else means the run ended with nothing owed.
|
|
355
|
+
actor="$(gtd next --json 2>/dev/null | jq -r '.actor // ""' 2>/dev/null || true)"
|
|
356
|
+
|
|
357
|
+
if [ "$rc" -ne 0 ] || [ -z "$actor" ] || [ "$actor" = human ]; then
|
|
358
|
+
report blocked
|
|
359
|
+
else
|
|
360
|
+
report idle
|
|
361
|
+
fi
|
|
362
|
+
|
|
363
|
+
exit $rc
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
A few things to know before relying on it:
|
|
367
|
+
|
|
368
|
+
- **Give `gtd` its own pane.** Run an interactive `claude` (or any agent whose
|
|
369
|
+
herdr integration reports session identity) in the same pane and that pane
|
|
370
|
+
owns a `herdr:claude` session until the process exits; while it does, the
|
|
371
|
+
wrapper's reports are silently ignored and the sidebar stops tracking `gtd`.
|
|
372
|
+
- **`blocked` covers the resting `idle` state too** — gtd's own `idle` is a
|
|
373
|
+
human gate (it waits for you to write a steering file), so a finished cycle
|
|
374
|
+
reads as "your turn", which is what it is.
|
|
375
|
+
- The status **persists after the wrapper exits** — that's the point: the
|
|
376
|
+
sidebar keeps showing which worktree is waiting on you. Hand the pane back to
|
|
377
|
+
ordinary detection with
|
|
378
|
+
`herdr pane release-agent "$HERDR_PANE_ID" --source custom:gtd --agent gtd`.
|
|
379
|
+
- A non-zero exit (a stall, a refusal, an error) reports `blocked`: herdr has no
|
|
380
|
+
failure state, and those all need a human.
|
|
381
|
+
- Ctrl-C reports `blocked` rather than leaving a stale `working`.
|
|
382
|
+
|
|
383
|
+
Optionally,
|
|
384
|
+
`herdr pane report-metadata "$HERDR_PANE_ID" --source custom:gtd-display --token summary=<text>`
|
|
385
|
+
sets a value renderable as `$summary` in an Agent sidebar row, if you want more
|
|
386
|
+
than the state itself.
|
|
387
|
+
|
|
302
388
|
## Configuration
|
|
303
389
|
|
|
304
390
|
gtd reads an optional `.gtdrc` config file via
|
|
@@ -483,9 +569,11 @@ compiler's job at load time.
|
|
|
483
569
|
per `review`-mode chunk that still has an unchecked hunk (an outline of the
|
|
484
570
|
packages left to review) plus check/uncheck actions over those chunks,
|
|
485
571
|
go-to-definition from a `review`-mode hunk line into the file it points at (at
|
|
486
|
-
its `#line`), symbols over a `qa`-mode file's open questions
|
|
487
|
-
|
|
488
|
-
|
|
572
|
+
its `#line`), symbols over a `qa`-mode file's open questions plus "pick this
|
|
573
|
+
option"/"uncheck this option" code actions on each option — offered anywhere on
|
|
574
|
+
the option's list item, including any wrapped continuation lines, not just its
|
|
575
|
+
own `- [ ]` line — diagnostics for both (live as you edit), and a
|
|
576
|
+
`gtd.openSteeringFile` command that jumps to the current state's steering file.
|
|
489
577
|
|
|
490
578
|
It is config-driven via each state's `file:`/`mode:`, and falls back to basename
|
|
491
579
|
dispatch (`REVIEW.md` → `review`) with no config in sight. `qa` and `review` are
|
package/bin/gtd
CHANGED
|
@@ -44,12 +44,12 @@ if [[ "$FANCY" == 1 ]]; then
|
|
|
44
44
|
C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m'
|
|
45
45
|
C_CYAN=$'\e[36m'; C_GREEN=$'\e[32m'; C_YELLOW=$'\e[33m'; C_RED=$'\e[31m'
|
|
46
46
|
M_TRANS="➡️"; M_AGENT="🤖"; M_CHECK="🔧"; M_HUMAN="✍️"
|
|
47
|
-
M_FIX="🔁"; M_COMMIT="✅"; M_SETTLE="🎉"; M_ERROR="❌"; M_ELLIPSIS="…"
|
|
47
|
+
M_FIX="🔁"; M_COMMIT="✅"; M_SETTLE="🎉"; M_ERROR="❌"; M_WARN="⚠️"; M_ELLIPSIS="…"
|
|
48
48
|
else
|
|
49
49
|
C_RESET=""; C_BOLD=""; C_DIM=""
|
|
50
50
|
C_CYAN=""; C_GREEN=""; C_YELLOW=""; C_RED=""
|
|
51
51
|
M_TRANS="->"; M_AGENT="[agent]"; M_CHECK="[check]"; M_HUMAN="[you]"
|
|
52
|
-
M_FIX="[fix]"; M_COMMIT="[commit]"; M_SETTLE="[done]"; M_ERROR="[err]"; M_ELLIPSIS="..."
|
|
52
|
+
M_FIX="[fix]"; M_COMMIT="[commit]"; M_SETTLE="[done]"; M_ERROR="[err]"; M_WARN="[warn]"; M_ELLIPSIS="..."
|
|
53
53
|
fi
|
|
54
54
|
|
|
55
55
|
# emit_file_rows <files> — render a commit's changed files as dim, indented
|
|
@@ -114,8 +114,10 @@ emit_error() {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
# emit_log_pointer — the "see the log" line printed right after every
|
|
117
|
-
# emit_error call, to stderr:
|
|
118
|
-
#
|
|
117
|
+
# emit_error call, to stderr: it names the log as the OVERFLOW channel — the
|
|
118
|
+
# failing execution's own output is already replayed inline (see emit_block/
|
|
119
|
+
# emit_output_since below); the log is where the rest of it, and the full
|
|
120
|
+
# run's other output, lives.
|
|
119
121
|
emit_log_pointer() {
|
|
120
122
|
if [[ "$FANCY" == 1 ]]; then
|
|
121
123
|
printf '%s📄 see %s%s\n' "$C_DIM" "$GTD_LOOP_LOG" "$C_RESET" >&2
|
|
@@ -124,6 +126,58 @@ emit_log_pointer() {
|
|
|
124
126
|
fi
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
# Suppressed subprocess output goes to $GTD_LOOP_LOG in full, but a FAILING
|
|
130
|
+
# execution also replays inline: mark the log's size before the call, print
|
|
131
|
+
# what was appended since. Bounded to the last $ERR_TAIL_LINES lines — the log
|
|
132
|
+
# holds the rest, and the overflow row says where.
|
|
133
|
+
ERR_TAIL_LINES=20
|
|
134
|
+
|
|
135
|
+
# log_offset — the log's current size in bytes (0 when it doesn't exist yet).
|
|
136
|
+
log_offset() {
|
|
137
|
+
local n=0
|
|
138
|
+
if [[ -n "${GTD_LOOP_LOG:-}" && -f "$GTD_LOOP_LOG" ]]; then n="$(wc -c <"$GTD_LOOP_LOG")"; fi
|
|
139
|
+
printf '%s' "${n// /}"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# emit_block <text> — replay captured subprocess output to stderr, dim and
|
|
143
|
+
# indented like emit_file_rows, capped at the LAST $ERR_TAIL_LINES lines with
|
|
144
|
+
# an overflow row naming the log. Empty <text> prints nothing. CSI escapes are
|
|
145
|
+
# stripped under FANCY=0 (the plain-ASCII promise), using a sed form (\e, not
|
|
146
|
+
# \x1b) that both BSD and GNU sed accept.
|
|
147
|
+
emit_block() {
|
|
148
|
+
local text="$1" total overflow
|
|
149
|
+
[[ -z "$text" ]] && return 0
|
|
150
|
+
if [[ "$FANCY" == 0 ]]; then
|
|
151
|
+
text="$(sed $'s/\e\\[[0-9;]*[a-zA-Z]//g' <<<"$text")"
|
|
152
|
+
fi
|
|
153
|
+
total="$(wc -l <<<"$text")"
|
|
154
|
+
total="${total// /}"
|
|
155
|
+
if ((total > ERR_TAIL_LINES)); then
|
|
156
|
+
overflow=$((total - ERR_TAIL_LINES))
|
|
157
|
+
printf ' %s%s (%d earlier lines in %s)%s\n' "$C_DIM" "$M_ELLIPSIS" "$overflow" "$GTD_LOOP_LOG" "$C_RESET" >&2
|
|
158
|
+
text="$(tail -n "$ERR_TAIL_LINES" <<<"$text")"
|
|
159
|
+
fi
|
|
160
|
+
while IFS= read -r line; do
|
|
161
|
+
printf ' %s%s%s\n' "$C_DIM" "$line" "$C_RESET" >&2
|
|
162
|
+
done <<<"$text"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# emit_output_since <offset> — emit_block over the log bytes appended since
|
|
166
|
+
# <offset>.
|
|
167
|
+
emit_output_since() {
|
|
168
|
+
local offset="$1" bytes=""
|
|
169
|
+
if [[ -n "${GTD_LOOP_LOG:-}" && -f "$GTD_LOOP_LOG" ]]; then
|
|
170
|
+
bytes="$(tail -c "+$((offset + 1))" "$GTD_LOOP_LOG" 2>/dev/null || true)"
|
|
171
|
+
fi
|
|
172
|
+
emit_block "$bytes"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# emit_warn <message> — a surfaced but NON-fatal failure (a check script that
|
|
176
|
+
# exited non-zero).
|
|
177
|
+
emit_warn() {
|
|
178
|
+
printf '%s %s%s%s\n' "$M_WARN" "$C_YELLOW" "$1" "$C_RESET" >&2
|
|
179
|
+
}
|
|
180
|
+
|
|
127
181
|
# ── Git-derivation helpers (parse_subject, report_commits) ──────────────────
|
|
128
182
|
|
|
129
183
|
# parse_subject <subject> — sets S_ACTOR / S_FROM / S_TO / S_KIND. Patterns are
|
|
@@ -375,9 +429,10 @@ fi
|
|
|
375
429
|
|
|
376
430
|
while true; do
|
|
377
431
|
if ! next_json="$(run_gtd next --json 2>&1)"; then
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
432
|
+
emit_error "could not determine the next step:
|
|
433
|
+
$next_json
|
|
434
|
+
Run \`gtd status\` to inspect the repo."
|
|
435
|
+
emit_log_pointer
|
|
381
436
|
exit 1
|
|
382
437
|
fi
|
|
383
438
|
|
|
@@ -394,17 +449,29 @@ while true; do
|
|
|
394
449
|
# (or an outer wrapper) edits and re-launches; the opening move above
|
|
395
450
|
# captures what they left.
|
|
396
451
|
emit_gate "$state" "$label"
|
|
397
|
-
run_gtd next
|
|
452
|
+
if ! gate_out="$(run_gtd next 2>&1)"; then
|
|
453
|
+
emit_error "$gate_out"
|
|
454
|
+
emit_log_pointer
|
|
455
|
+
exit 1
|
|
456
|
+
fi
|
|
457
|
+
printf '%s\n' "$gate_out"
|
|
398
458
|
exit 0
|
|
399
459
|
fi
|
|
400
460
|
|
|
401
461
|
if [[ "$kind" == "script" ]]; then
|
|
402
462
|
emit_action "$M_CHECK" "$label"
|
|
403
|
-
# Execute the emitted wrapper verbatim; its exit code
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
#
|
|
407
|
-
|
|
463
|
+
# Execute the emitted wrapper verbatim; its exit code decides nothing
|
|
464
|
+
# (the script encodes its outcome in the tree, and `gtd step` captures
|
|
465
|
+
# whatever it left there via the state's own `on` patterns) but a non-zero
|
|
466
|
+
# exit is surfaced as a warning — its own output goes to the log, and to
|
|
467
|
+
# the terminal too when it exits non-zero.
|
|
468
|
+
script_offset="$(log_offset)"
|
|
469
|
+
script_status=0
|
|
470
|
+
bash -c "$content" >>"$GTD_LOOP_LOG" 2>&1 || script_status=$?
|
|
471
|
+
if ((script_status != 0)); then
|
|
472
|
+
emit_warn "the check script at '$state' exited $script_status — continuing; the outcome is whatever it left in the tree:"
|
|
473
|
+
emit_output_since "$script_offset"
|
|
474
|
+
fi
|
|
408
475
|
if ! step_out="$(run_gtd step "$actor" 2>&1)"; then
|
|
409
476
|
printf '%s\n' "$step_out" >>"$GTD_LOOP_LOG"
|
|
410
477
|
emit_error "$step_out"
|
|
@@ -478,24 +545,42 @@ $next_out"
|
|
|
478
545
|
# driver-side equivalent, so the next rest is only ever handed a well-formed
|
|
479
546
|
# file. gtd itself never validates at step time — this is the loop's job.
|
|
480
547
|
emit_action "$M_AGENT" "$label"
|
|
481
|
-
|
|
548
|
+
turn_offset="$(log_offset)"
|
|
549
|
+
agent_status=0
|
|
550
|
+
run_agent_turn "$content" "$resume" || agent_status=$?
|
|
551
|
+
if ((agent_status != 0)); then
|
|
552
|
+
emit_error "the agent turn at '$state' failed (exit $agent_status):"
|
|
553
|
+
emit_output_since "$turn_offset"
|
|
554
|
+
emit_log_pointer
|
|
555
|
+
exit 1
|
|
556
|
+
fi
|
|
482
557
|
|
|
483
558
|
fix_attempt=0
|
|
484
559
|
while ! validate_out="$(run_gtd validate 2>&1)"; do
|
|
485
560
|
fix_attempt=$((fix_attempt + 1))
|
|
486
561
|
if ((fix_attempt > 3)); then
|
|
562
|
+
printf '%s\n' "$validate_out" >>"$GTD_LOOP_LOG"
|
|
487
563
|
emit_error "'$state' still fails \`gtd validate\` after 3 fix attempts:
|
|
488
564
|
$validate_out
|
|
489
565
|
Stopping rather than stepping with a malformed steering file."
|
|
490
566
|
emit_log_pointer
|
|
491
567
|
exit 1
|
|
492
568
|
fi
|
|
569
|
+
printf '%s\n' "$validate_out" >>"$GTD_LOOP_LOG"
|
|
570
|
+
emit_action "$M_FIX" "attempt $fix_attempt"
|
|
493
571
|
# Resume the same session when there is one (the agent just wrote the file);
|
|
494
572
|
# otherwise a fresh invocation still gets the findings via the prompt.
|
|
495
573
|
fix_resume=0
|
|
496
574
|
[[ -n "$session_id" ]] && fix_resume=1
|
|
497
|
-
|
|
498
|
-
|
|
575
|
+
turn_offset="$(log_offset)"
|
|
576
|
+
agent_status=0
|
|
577
|
+
run_agent_turn "$content"$'\n\n'"Your last turn does not pass \`gtd validate\`. Fix these format violations, then finish:"$'\n'"$validate_out" "$fix_resume" || agent_status=$?
|
|
578
|
+
if ((agent_status != 0)); then
|
|
579
|
+
emit_error "the agent's fix attempt $fix_attempt at '$state' failed (exit $agent_status):"
|
|
580
|
+
emit_output_since "$turn_offset"
|
|
581
|
+
emit_log_pointer
|
|
582
|
+
exit 1
|
|
583
|
+
fi
|
|
499
584
|
done
|
|
500
585
|
|
|
501
586
|
if ! step_out="$(run_gtd step "$actor" 2>&1)"; then
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -204397,11 +204397,28 @@ const splitQuestionBlocks = (lines, start) => {
|
|
|
204397
204397
|
/** A markdown task-list checkbox line: `- [ ]` / `- [x]` / `* [X]`, optional leading indent, optional trailing text. */
|
|
204398
204398
|
const CHECKBOX_RE = /^\s*[-*]\s*\[([ xX])\]\s?(.*)$/;
|
|
204399
204399
|
/**
|
|
204400
|
+
* The body index of the last line belonging to the list item that starts at
|
|
204401
|
+
* `index`: the run of following lines that are neither blank nor a checkbox of
|
|
204402
|
+
* their own (see the span rule in the module doc). Indentation is NOT
|
|
204403
|
+
* required — an unindented lazy wrap is as much part of the item as an
|
|
204404
|
+
* indented one.
|
|
204405
|
+
*/
|
|
204406
|
+
const itemEndIndex = (body, index) => {
|
|
204407
|
+
let end = index;
|
|
204408
|
+
for (let i = index + 1; i < body.length; i += 1) {
|
|
204409
|
+
const line = body[i];
|
|
204410
|
+
if (line.trim().length === 0 || CHECKBOX_RE.test(line)) break;
|
|
204411
|
+
end = i;
|
|
204412
|
+
}
|
|
204413
|
+
return end;
|
|
204414
|
+
};
|
|
204415
|
+
/**
|
|
204400
204416
|
* Extracts the checkbox options from a question block's body, in document
|
|
204401
204417
|
* order. The LAST option is the free-text slot (`freeText: true`); its text is
|
|
204402
204418
|
* normalized to `""` when it still carries the unfilled `FREE_TEXT_PLACEHOLDER`.
|
|
204403
204419
|
* `bodyStart` is the absolute 0-based line index of `body[0]` (the line right
|
|
204404
|
-
* after the `###` heading), so each option carries its true source line
|
|
204420
|
+
* after the `###` heading), so each option carries its true source line and
|
|
204421
|
+
* span (`itemEndIndex`).
|
|
204405
204422
|
*/
|
|
204406
204423
|
const parseOptions = (body, bodyStart) => {
|
|
204407
204424
|
const raw = [];
|
|
@@ -204411,7 +204428,8 @@ const parseOptions = (body, bodyStart) => {
|
|
|
204411
204428
|
raw.push({
|
|
204412
204429
|
checked: match[1] !== " ",
|
|
204413
204430
|
text: match[2].trim(),
|
|
204414
|
-
sourceLine: bodyStart + i
|
|
204431
|
+
sourceLine: bodyStart + i,
|
|
204432
|
+
bodyIndex: i
|
|
204415
204433
|
});
|
|
204416
204434
|
});
|
|
204417
204435
|
const lastIndex = raw.length - 1;
|
|
@@ -204422,7 +204440,8 @@ const parseOptions = (body, bodyStart) => {
|
|
|
204422
204440
|
checked: option.checked,
|
|
204423
204441
|
text: normalized,
|
|
204424
204442
|
freeText,
|
|
204425
|
-
sourceLine: option.sourceLine
|
|
204443
|
+
sourceLine: option.sourceLine,
|
|
204444
|
+
endLine: bodyStart + itemEndIndex(body, option.bodyIndex)
|
|
204426
204445
|
};
|
|
204427
204446
|
});
|
|
204428
204447
|
};
|
|
@@ -204704,7 +204723,7 @@ const questionSymbols = (content) => {
|
|
|
204704
204723
|
const children = question.options.map((option) => ({
|
|
204705
204724
|
name: `${option.checked ? "[x]" : "[ ]"} ${option.text || "your answer"}`,
|
|
204706
204725
|
kind: import_main.SymbolKind.Boolean,
|
|
204707
|
-
range:
|
|
204726
|
+
range: spanRange(lines, option.sourceLine, option.endLine),
|
|
204708
204727
|
selectionRange: lineRange(lines, option.sourceLine)
|
|
204709
204728
|
}));
|
|
204710
204729
|
return {
|
|
@@ -204886,11 +204905,12 @@ const optionCodeAction = (uri, content, question, option) => {
|
|
|
204886
204905
|
};
|
|
204887
204906
|
};
|
|
204888
204907
|
/**
|
|
204889
|
-
* Code actions for a `qa`-mode file: on an open question's option
|
|
204890
|
-
*
|
|
204891
|
-
*
|
|
204892
|
-
*
|
|
204893
|
-
*
|
|
204908
|
+
* Code actions for a `qa`-mode file: anywhere on an open question's option's
|
|
204909
|
+
* list item — its `- [ ]` line or any of its wrapped continuation lines (see
|
|
204910
|
+
* `QuestionOption.endLine`) — "pick this option" (radio semantics — check it
|
|
204911
|
+
* and uncheck every sibling in the same question, so exactly one stays
|
|
204912
|
+
* ticked) or "uncheck this option" when it is already the chosen one. No
|
|
204913
|
+
* action off an option's span, or on an answered-section (prose) question.
|
|
204894
204914
|
*/
|
|
204895
204915
|
const questionCodeActions = (uri, content, range) => {
|
|
204896
204916
|
const { questions } = parseOpenQuestions(content);
|
|
@@ -204898,7 +204918,7 @@ const questionCodeActions = (uri, content, range) => {
|
|
|
204898
204918
|
const actions = [];
|
|
204899
204919
|
for (const question of questions) {
|
|
204900
204920
|
if (question.status !== "open") continue;
|
|
204901
|
-
const option = question.options.find((o) => o.sourceLine
|
|
204921
|
+
const option = question.options.find((o) => cursorLine >= o.sourceLine && cursorLine <= o.endLine);
|
|
204902
204922
|
if (!option) continue;
|
|
204903
204923
|
const action = optionCodeAction(uri, content, question, option);
|
|
204904
204924
|
if (action) actions.push(action);
|