plumbbob 0.12.1 → 0.13.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/.claude-plugin/plugin.json +1 -1
- package/README.md +20 -13
- package/dist/cli-core.js +22 -6
- package/dist/lib/git.js +20 -8
- package/dist/lib/latch.js +23 -5
- package/dist/lib/notice.js +18 -3
- package/dist/lib/orient.js +350 -34
- package/dist/lib/sidecar.js +202 -3
- package/dist/lib/steplist.js +128 -0
- package/dist/verbs/abandon.js +19 -14
- package/dist/verbs/build.js +73 -14
- package/dist/verbs/checkpoint.js +31 -21
- package/dist/verbs/doctor.js +23 -4
- package/dist/verbs/finish.js +21 -17
- package/dist/verbs/handoff.js +259 -80
- package/dist/verbs/order.js +32 -20
- package/dist/verbs/park.js +6 -2
- package/dist/verbs/recover.js +31 -1
- package/dist/verbs/relay.js +306 -0
- package/dist/verbs/revert.js +17 -11
- package/dist/verbs/spike.js +50 -38
- package/dist/verbs/start.js +13 -9
- package/dist/verbs/status.js +2 -2
- package/dist/verbs/turn.js +90 -53
- package/dist/verbs/use.js +21 -17
- package/hooks/hooks.json +7 -0
- package/package.json +1 -1
- package/skills/build/SKILL.md +107 -49
- package/skills/harvest/SKILL.md +10 -1
- package/skills/order/SKILL.md +20 -10
- package/skills/park/SKILL.md +7 -0
- package/skills/plan/SKILL.md +52 -7
- package/skills/refine/SKILL.md +21 -3
- package/skills/status/SKILL.md +8 -3
- package/skills/step/SKILL.md +16 -3
- package/skills/verify/SKILL.md +35 -18
- package/templates/intent.md +23 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plumbbob",
|
|
3
3
|
"displayName": "PlumbBob",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.1",
|
|
5
5
|
"description": "Attention-first build process — driver skills + a CLI that keep you the decider: guidance on the work, a latch on the record.",
|
|
6
6
|
"author": { "name": "Rob McLarty", "email": "hello@robmclarty.com", "url": "https://robmclarty.com" },
|
|
7
7
|
"homepage": "https://github.com/robmclarty/plumbbob#readme",
|
package/README.md
CHANGED
|
@@ -131,8 +131,8 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
131
131
|
|
|
132
132
|
1. **Plan.** Fire `/plumbbob:plan` and give it whatever you have: nothing (it interviews
|
|
133
133
|
you), a rough line (`/plumbbob:plan rate-limit POST /login, 5/min/IP, return 429`), or a
|
|
134
|
-
path to a spec file. Together you fill the build's `intent.md
|
|
135
|
-
yet.
|
|
134
|
+
path to a spec file. Together you fill the build's `intent.md`: the steps, and the
|
|
135
|
+
build ranges that say where the build stops for your call. No code is written yet.
|
|
136
136
|
2. **Build.** Fire `/plumbbob:build`. It implements the next undone step, runs the
|
|
137
137
|
heavy check gate, reviews its own diff against the plan, and stops. What it stops on
|
|
138
138
|
is one block with a fixed shape (the [turn anatomy](docs/presentation.md)): the CLI
|
|
@@ -140,12 +140,14 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
140
140
|
nothing is written around it.
|
|
141
141
|
|
|
142
142
|
````markdown
|
|
143
|
+
**Now**: Step 1 of 3 - feat(limiter): add a token-bucket limiter
|
|
144
|
+
|
|
143
145
|
**Summary**: A per-IP token bucket now refuses the 6th attempt inside a minute. (details: `.plumbbob/detail.md`)
|
|
144
146
|
|
|
145
147
|
1. The bucket lives in memory, keyed by IP, refilling five tokens a minute.
|
|
146
148
|
2. The clock is injected, so the test advances time instead of sleeping on it.
|
|
147
149
|
|
|
148
|
-
**Readout**:
|
|
150
|
+
**Readout**:
|
|
149
151
|
|
|
150
152
|
```text
|
|
151
153
|
check green: 7 of 7 checks
|
|
@@ -164,7 +166,7 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
164
166
|
**Your Call**:
|
|
165
167
|
|
|
166
168
|
- `looks good` → I checkpoint Step 1; back to the boundary
|
|
167
|
-
- `expand`, or any question → I show more of what is there; nothing changes
|
|
169
|
+
- `expand`, or any question → I show more of what is there; nothing changes, and `show the pause` brings all of this back
|
|
168
170
|
- anything that reads as direction → I take it as what to change; nothing lands until you approve
|
|
169
171
|
- `revert` → I wind the work back to the last checkpoint
|
|
170
172
|
|
|
@@ -192,9 +194,13 @@ In Claude Code, inside any git repo with a clean tree:
|
|
|
192
194
|

|
|
193
195
|
|
|
194
196
|
Ask instead (`expand 2`, or any question) and it shows more of what is there and
|
|
195
|
-
changes nothing
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
changes nothing. The answer closes on one line pointing back at the step, not on the
|
|
198
|
+
whole block again, since you have already read it; say `show the pause` to see the
|
|
199
|
+
block again. Direct it and it takes that as the fix, and nothing lands until you say
|
|
200
|
+
`looks good`. Then fire `/plumbbob:build` again for the next step: re-firing it
|
|
201
|
+
*is* the clock tick. When the plan cut build ranges, the line beneath Next Up names
|
|
202
|
+
one to type instead (`· range 2 of 2: /plumbbob:build 2-3`): it lands each step it
|
|
203
|
+
names and stops where the plan reserved a call for you. Whenever you lose the thread, `/plumbbob:status` shows where you
|
|
198
204
|
are and names the next move.
|
|
199
205
|
4. **Finish.** When the last step is done, `/plumbbob:finish` writes a report of what shipped
|
|
200
206
|
and why into the build's tracked folder, makes the final commit, and clears the slate
|
|
@@ -220,8 +226,8 @@ nothing else:
|
|
|
220
226
|
|
|
221
227
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
222
228
|
| ------------------------------------ | ------ |
|
|
223
|
-
| `/plumbbob:plan` | plan the whole goal: open the session and author intent's Frame, Decisions, Constraints, **and all Steps
|
|
224
|
-
| `/plumbbob:build` | implement the next planned step, then verify it to the pause: `--auto` self-approves and chains to done; a range like `1-3` self-approves
|
|
229
|
+
| `/plumbbob:plan` | plan the whole goal: open the session and author intent's Frame, Decisions, Constraints, **and all Steps**, with the build ranges that say where the build stops for you |
|
|
230
|
+
| `/plumbbob:build` | implement the next planned step, then verify it to the pause: `--auto` self-approves and chains to done; a range like `1-3`, or a list like `20,5-9`, self-approves exactly the steps it names, then stops |
|
|
225
231
|
| `/plumbbob:finish` | finish up: write the report, make the final commit, clear for a fresh goal |
|
|
226
232
|
|
|
227
233
|
**Plan-shaping moves**: optional, for when the plan needs work mid-flight:
|
|
@@ -229,7 +235,7 @@ nothing else:
|
|
|
229
235
|
| Skill <img alt="" width="110" height="1"> | Does |
|
|
230
236
|
| ------------------------------------ | ------ |
|
|
231
237
|
| `/plumbbob:step` | revise/sharpen the next step (empty input auto-syncs it to reality) |
|
|
232
|
-
| `/plumbbob:refine` | attack the frame for holes, or repair a drifted plan |
|
|
238
|
+
| `/plumbbob:refine` | attack the frame for holes, or repair a drifted plan (`ranges` re-cuts the build ranges) |
|
|
233
239
|
| `/plumbbob:spike` | throwaway worktree experiment for a fork the plan can't settle |
|
|
234
240
|
|
|
235
241
|
**Helpers**: orient, verify, recover, diagnose:
|
|
@@ -254,9 +260,10 @@ All fourteen, with inputs and effects, are in
|
|
|
254
260
|
[`docs/skills-reference.md`](docs/skills-reference.md).
|
|
255
261
|
|
|
256
262
|
Under the skills ships a lean `plumbbob` CLI (the mechanical verbs the
|
|
257
|
-
skills shell out to),
|
|
258
|
-
ask-hook while a step is in flight,
|
|
259
|
-
reads
|
|
263
|
+
skills shell out to), four hooks (a non-blocking post-edit lint, a `git commit`
|
|
264
|
+
ask-hook while a step is in flight, the per-prompt turn ledger the approval latch
|
|
265
|
+
reads, and a check at each turn's end that the turn relayed the ending plumbbob
|
|
266
|
+
printed), and a `.plumbbob/` sidecar you can open and edit by hand at any time: a tracked
|
|
260
267
|
`builds/<slug>/` folder per build (`intent.md`, `build-log.md`, `checkpoints`, `report.md`)
|
|
261
268
|
that rides the branch into the PR, plus an untracked control plane (`settings.local.json`,
|
|
262
269
|
the session sentinel, the in-flight markers).
|
package/dist/cli-core.js
CHANGED
|
@@ -28,6 +28,7 @@ import { init } from "./verbs/init.js";
|
|
|
28
28
|
import { doctor } from "./verbs/doctor.js";
|
|
29
29
|
import { recover } from "./verbs/recover.js";
|
|
30
30
|
import { agent } from "./verbs/agent.js";
|
|
31
|
+
import { relay } from "./verbs/relay.js";
|
|
31
32
|
import { turn } from "./verbs/turn.js";
|
|
32
33
|
/** Shared across every verb that resolves through the active-build cursor. */
|
|
33
34
|
const BUILD_FLAG = {
|
|
@@ -80,15 +81,16 @@ const VERBS = [
|
|
|
80
81
|
},
|
|
81
82
|
{
|
|
82
83
|
name: 'handoff',
|
|
83
|
-
description: 'print the footer card that ends a turn (pause, boundary, plan, or driver)
|
|
84
|
-
synopsis: ['handoff [<n>] [--plan] [--driver] [--build <slug>]'],
|
|
84
|
+
description: 'print the footer card that ends a turn (pause, boundary, plan, or driver), or a reminder for a pause already shown',
|
|
85
|
+
synopsis: ['handoff [<n>] [--full] [--plan] [--driver] [--build <slug>]'],
|
|
85
86
|
args: [{ name: '<n>', gloss: 'step to report on (default: the in-flight step, else the last checkpointed one)' }],
|
|
86
87
|
flags: [
|
|
88
|
+
{ name: '--full', gloss: 'print the whole pause card even when that pause was already shown' },
|
|
87
89
|
{ name: '--plan', gloss: 'render the plan-pause card instead of a step tier' },
|
|
88
90
|
{ name: '--driver', gloss: "render a driver turn's pointer back at the open step" },
|
|
89
91
|
BUILD_FLAG,
|
|
90
92
|
],
|
|
91
|
-
notes: '
|
|
93
|
+
notes: 'Writes only the detail file (a blank line around each heading) and the PAUSE stamp; refuses (exit 1) with no session, or with --plan and --driver together.',
|
|
92
94
|
},
|
|
93
95
|
{
|
|
94
96
|
name: 'check',
|
|
@@ -144,10 +146,15 @@ const VERBS = [
|
|
|
144
146
|
{
|
|
145
147
|
name: 'order',
|
|
146
148
|
description: 'set the build order: the undone steps in the sequence to build them',
|
|
147
|
-
synopsis: ['order <
|
|
148
|
-
args: [
|
|
149
|
+
synopsis: ['order <steps>', 'order --reset'],
|
|
150
|
+
args: [
|
|
151
|
+
{
|
|
152
|
+
name: '<steps>',
|
|
153
|
+
gloss: 'undone step numbers and spans (2-4) in the sequence to build them, space- or comma-separated (1, 6, 2-4, 7, 5); the rest follow in document order',
|
|
154
|
+
},
|
|
155
|
+
],
|
|
149
156
|
flags: [{ name: '--reset', gloss: 'drop the line, so the plan reads in document order again' }],
|
|
150
|
-
notes: 'Writes the one numeric line under `## Build order` in intent.md, which status, build, checkpoint, and handoff all read. Refuses (exit 1) with no session, no numbers, a number the plan lacks, a repeat, or a step already checkpointed.',
|
|
157
|
+
notes: 'Writes the one numeric line under `## Build order` in intent.md, which status, build, checkpoint, and handoff all read. A span is spelled out on the line. Refuses (exit 1) with no session, no numbers, a span that runs backward, a number the plan lacks, a repeat, or a step already checkpointed.',
|
|
151
158
|
},
|
|
152
159
|
{
|
|
153
160
|
name: 'spike',
|
|
@@ -225,6 +232,13 @@ const VERBS = [
|
|
|
225
232
|
tolerateUnknownFlags: true,
|
|
226
233
|
notes: 'Reads the hook payload from stdin and always exits 0 — a broken hook must never wedge a prompt.',
|
|
227
234
|
},
|
|
235
|
+
{
|
|
236
|
+
name: 'relay',
|
|
237
|
+
description: 'Stop hook machinery: check that the turn carried the ending it printed (not a user verb)',
|
|
238
|
+
synopsis: ['relay'],
|
|
239
|
+
tolerateUnknownFlags: true,
|
|
240
|
+
notes: "Reads the hook payload from stdin and always exits 0: a broken hook must never wedge a turn's end.",
|
|
241
|
+
},
|
|
228
242
|
];
|
|
229
243
|
/**
|
|
230
244
|
* Render the verb table for `plumbbob help`.
|
|
@@ -379,6 +393,8 @@ async function dispatch(verb, cwd, rest) {
|
|
|
379
393
|
return agent(cwd, rest);
|
|
380
394
|
case 'turn':
|
|
381
395
|
return turn(cwd, rest);
|
|
396
|
+
case 'relay':
|
|
397
|
+
return relay(cwd, rest);
|
|
382
398
|
default:
|
|
383
399
|
process.stderr.write(`plumbbob: unknown verb '${verb}'. Run 'plumbbob help' for the verb table.\n`);
|
|
384
400
|
return 1;
|
package/dist/lib/git.js
CHANGED
|
@@ -68,18 +68,30 @@ export function hasCommit(root) {
|
|
|
68
68
|
export function isDirty(root) {
|
|
69
69
|
return runGit(root, ['status', '--porcelain']).length > 0;
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The body line that marks a plan commit made by hand (a `chore(plan)`
|
|
73
|
+
* harvest). A harvest lands between nearly every step, so counting it as
|
|
74
|
+
* out-of-band would trip the advisory on routine housekeeping and stop meaning
|
|
75
|
+
* anything within three steps. `status` and `handoff` both pass it to
|
|
76
|
+
* `commitsSince`, which is what keeps the two counts one quantity.
|
|
77
|
+
*/
|
|
78
|
+
export const PLAN_COMMIT_MARKER = '^plumbbob plan$';
|
|
71
79
|
/**
|
|
72
80
|
* The number of commits on HEAD not reachable from `sha`: `git rev-list
|
|
73
81
|
* --count --first-parent <sha>..HEAD`.
|
|
74
82
|
*
|
|
75
|
-
* `status`
|
|
76
|
-
* landed since the last checkpoint outside
|
|
77
|
-
* commits are surfaced, never blocked).
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
+
* `status` and `handoff` both read this for their out-of-band count: one
|
|
84
|
+
* neutral note when commits landed since the last checkpoint outside
|
|
85
|
+
* plumbbob's ledger (out-of-band commits are surfaced, never blocked). Two
|
|
86
|
+
* mechanisms keep plumbbob's own commits out of that count. A commit any
|
|
87
|
+
* plumbbob verb makes is recorded in the ledger and becomes the anchor, so the
|
|
88
|
+
* range starts after it. A plan commit made by hand carries
|
|
89
|
+
* `PLAN_COMMIT_MARKER` on a line of its body, and both callers pass it as
|
|
90
|
+
* `excluding` so the range drops it. `--first-parent` keeps the count on the
|
|
91
|
+
* branch's own line: merging upstream reads as the one merge commit, not the
|
|
92
|
+
* dozens it carried; those didn't land "outside the ledger" in any sense the
|
|
93
|
+
* receipt should nag about. `excluding` is a commit-message pattern whose
|
|
94
|
+
* matches are left out of the count.
|
|
83
95
|
*
|
|
84
96
|
* Best-effort and never throws: 0 when the range is empty, `sha` is unknown to
|
|
85
97
|
* the repo, or HEAD is unborn: the count is informational (the human commits
|
package/dist/lib/latch.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { readFileSync } from 'node:fs';
|
|
18
18
|
import { grantPath, tickPath, turnPath } from "./sidecar.js";
|
|
19
19
|
import { resolveBoolean } from "./settings.js";
|
|
20
|
+
import { hasStep, normalizeStepList, parseStepList } from "./steplist.js";
|
|
20
21
|
const ALLOW = { allow: true };
|
|
21
22
|
/**
|
|
22
23
|
* The refusal message is the affordance; it tells the model what the pause is,
|
|
@@ -51,12 +52,20 @@ const SETTINGS_AUTO_NOTE = `note: \`auto\` is set in settings but is no longer a
|
|
|
51
52
|
function ceilingMessage(ceiling) {
|
|
52
53
|
return `plumbbob: checkpoint refused — the range you granted ends at step ${ceiling} — pause here; run it again to continue.\n`;
|
|
53
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The refusal for a step a list grant does not name, naming the steps it does.
|
|
57
|
+
*/
|
|
58
|
+
function outsideMessage(step, steps) {
|
|
59
|
+
return `plumbbob: checkpoint refused — step ${step} is not in the range you granted (${normalizeStepList(steps)}) — pause here; run it again to continue.\n`;
|
|
60
|
+
}
|
|
54
61
|
/**
|
|
55
62
|
* The five-row matrix, in order, first hit wins.
|
|
56
63
|
*
|
|
57
|
-
* A
|
|
64
|
+
* A range grant speaks only to numbered steps: on a `--plan` checkpoint (step
|
|
58
65
|
* null) it neither allows nor refuses, and the decision falls through to the
|
|
59
|
-
* turn comparison.
|
|
66
|
+
* turn comparison. A `steps` grant lands exactly the steps the human typed and
|
|
67
|
+
* refuses any other, so no later edit to the plan or its build order can widen
|
|
68
|
+
* it; both kinds refuse with the `ceiling` reason, a step outside the range.
|
|
60
69
|
*/
|
|
61
70
|
export function evaluateLatch(input) {
|
|
62
71
|
if (input.isTTY)
|
|
@@ -65,6 +74,11 @@ export function evaluateLatch(input) {
|
|
|
65
74
|
return ALLOW; // 2: dormant ledger / hand-built diff
|
|
66
75
|
if (input.grant?.kind === 'auto')
|
|
67
76
|
return ALLOW; // 3: one-turn grant the human typed
|
|
77
|
+
if (input.grant?.kind === 'steps' && input.step !== null) {
|
|
78
|
+
if (hasStep(input.grant.steps, input.step))
|
|
79
|
+
return ALLOW;
|
|
80
|
+
return { allow: false, reason: 'ceiling', message: outsideMessage(input.step, input.grant.steps) };
|
|
81
|
+
}
|
|
68
82
|
if (input.grant?.kind === 'range' && input.step !== null) {
|
|
69
83
|
if (input.step <= input.grant.ceiling)
|
|
70
84
|
return ALLOW;
|
|
@@ -78,15 +92,19 @@ export function evaluateLatch(input) {
|
|
|
78
92
|
return { allow: false, reason: 'no-turn', message: input.settingsAuto ? base + SETTINGS_AUTO_NOTE : base };
|
|
79
93
|
}
|
|
80
94
|
/**
|
|
81
|
-
* Parse a GRANT file's content (`auto` | `range <M>`).
|
|
95
|
+
* Parse a GRANT file's content (`auto` | `steps <list>` | `range <M>`).
|
|
82
96
|
*
|
|
83
|
-
* Anything else (including a corrupted or hand-mangled file
|
|
84
|
-
* nothing: no grant, never an error.
|
|
97
|
+
* Anything else (including a corrupted or hand-mangled file, or a list that
|
|
98
|
+
* does not parse) contributes nothing: no grant, never an error.
|
|
85
99
|
*/
|
|
86
100
|
export function parseGrant(raw) {
|
|
87
101
|
const trimmed = raw.trim();
|
|
88
102
|
if (trimmed === 'auto')
|
|
89
103
|
return { kind: 'auto' };
|
|
104
|
+
const listed = /^steps (\S+)$/.exec(trimmed);
|
|
105
|
+
const steps = listed === null ? null : parseStepList(listed[1] ?? '');
|
|
106
|
+
if (steps !== null)
|
|
107
|
+
return { kind: 'steps', steps };
|
|
90
108
|
const range = /^range (\d+)$/.exec(trimmed);
|
|
91
109
|
if (range !== null)
|
|
92
110
|
return { kind: 'range', ceiling: Number(range[1]) };
|
package/dist/lib/notice.js
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
// the edge, and an optional indented arrow line carrying what to do next. One
|
|
12
12
|
// shape, so moving it is one edit rather than a sweep. The pointer at the next
|
|
13
13
|
// step is not a line's job: `handoff` renders one, and the verb prints it as
|
|
14
|
-
// the ending's last part.
|
|
14
|
+
// the ending's last part. The formatters are pure string assembly, no fs, no
|
|
15
|
+
// gate; `printEnding` is the one function here that writes, and every ending
|
|
16
|
+
// a verb prints goes through it so the relay check knows what was said.
|
|
17
|
+
import { writeRelay } from "./sidecar.js";
|
|
15
18
|
/** The column budget a notice line aims to fit, matching the readout fence. */
|
|
16
19
|
const WIDTH = 80;
|
|
17
20
|
/** The fewest detail items a degraded parenthetical still names, so the count has a universe to size. */
|
|
@@ -64,13 +67,14 @@ export function advisory(a) {
|
|
|
64
67
|
}
|
|
65
68
|
/**
|
|
66
69
|
* Assemble a transition's whole ending: the labeled lead line, the Verdict
|
|
67
|
-
* where one was measured, the advisories,
|
|
70
|
+
* where one was measured, the advisories, the Stop part where a range landed,
|
|
71
|
+
* and the pointer.
|
|
68
72
|
*
|
|
69
73
|
* The order is the type's field order, so a verb composes parts and never a
|
|
70
74
|
* sequence. A part with nothing in it vanishes rather than leaving a gap.
|
|
71
75
|
*/
|
|
72
76
|
export function ending(e) {
|
|
73
|
-
return blocks([e.lead, e.verdict ?? null, ...(e.advisories ?? []), e.pointer ?? null]);
|
|
77
|
+
return blocks([e.lead, e.verdict ?? null, ...(e.advisories ?? []), e.stop ?? null, e.pointer ?? null]);
|
|
74
78
|
}
|
|
75
79
|
/**
|
|
76
80
|
* Stack a turn's parts: one blank line between each, one beneath the last.
|
|
@@ -83,6 +87,17 @@ export function blocks(parts) {
|
|
|
83
87
|
const kept = parts.filter((p) => p !== null && p.trim().length > 0);
|
|
84
88
|
return `${kept.map((p) => p.replace(/\n+$/, '')).join('\n\n')}\n\n`;
|
|
85
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Print an ending to stdout and record it in RELAY, for the Stop check that
|
|
92
|
+
* the turn closed on it.
|
|
93
|
+
*
|
|
94
|
+
* Stdout first, and the record never throws: a failed write leaves the
|
|
95
|
+
* printed ending and the verb's exit code exactly as they would have been.
|
|
96
|
+
*/
|
|
97
|
+
export function printEnding(root, relay) {
|
|
98
|
+
process.stdout.write(relay.text);
|
|
99
|
+
writeRelay(root, relay);
|
|
100
|
+
}
|
|
86
101
|
/**
|
|
87
102
|
* The head, its trailing parenthetical, and the remedy line beneath, assembled.
|
|
88
103
|
*
|