plumbbob 0.13.5 → 0.13.6

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plumbbob",
3
3
  "displayName": "PlumbBob",
4
- "version": "0.13.5",
4
+ "version": "0.13.6",
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",
@@ -2029,13 +2029,64 @@ function sectionProse(detail, heading) {
2029
2029
  if (text.length === 0) {
2030
2030
  return null;
2031
2031
  }
2032
- return text
2033
- .split(/\n{2,}/)
2034
- .map((paragraph) => paragraph
2035
- .split('\n')
2036
- .map((l) => l.trim())
2037
- .join(' '))
2038
- .join('\n\n');
2032
+ return proseBlocks(text).join('\n\n');
2033
+ }
2034
+ /**
2035
+ * Prose's blocks, in order: each paragraph unwrapped onto one line, and each
2036
+ * fenced block kept line for line with its fences trimmed flush.
2037
+ *
2038
+ * A command the human is meant to copy rides a fence of its own
2039
+ * (D114 (command-fences)), and joining its lines would run two commands into
2040
+ * one. A fence opened straight under a sentence still ends that paragraph, so
2041
+ * it always stands between blank lines and renders as a block, never as
2042
+ * backticks inside the sentence. A fence left unclosed keeps everything after
2043
+ * it and is closed here, so it cannot swallow the `more details` link the
2044
+ * card prints beneath it.
2045
+ */
2046
+ function proseBlocks(text) {
2047
+ const blocks = [];
2048
+ let paragraph = [];
2049
+ let fence = null;
2050
+ let fenced = [];
2051
+ for (const line of text.split('\n')) {
2052
+ if (fence !== null) {
2053
+ const closed = closesFence(line, fence);
2054
+ fenced.push(closed ? line.trim() : line.trimEnd());
2055
+ if (closed) {
2056
+ blocks.push(fenced.join('\n'));
2057
+ fence = null;
2058
+ }
2059
+ continue;
2060
+ }
2061
+ const opened = fenceOpening(line);
2062
+ if (opened !== null || line.trim() === '') {
2063
+ if (paragraph.length > 0)
2064
+ blocks.push(paragraph.join(' '));
2065
+ paragraph = [];
2066
+ }
2067
+ if (opened !== null) {
2068
+ fence = opened;
2069
+ fenced = [line.trim()];
2070
+ }
2071
+ else if (line.trim() !== '') {
2072
+ paragraph.push(line.trim());
2073
+ }
2074
+ }
2075
+ if (fence !== null)
2076
+ blocks.push([...fenced, fence.char.repeat(fence.length)].join('\n'));
2077
+ if (paragraph.length > 0)
2078
+ blocks.push(paragraph.join(' '));
2079
+ return blocks;
2080
+ }
2081
+ /** The fence a line opens: three or more backticks or tildes, indented three spaces at most. Null when it opens none. */
2082
+ function fenceOpening(line) {
2083
+ const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
2084
+ return marker === undefined ? null : { char: marker.charAt(0), length: marker.length };
2085
+ }
2086
+ /** Whether a line closes an open fence: the same character, at least as many of it, and nothing after. */
2087
+ function closesFence(line, fence) {
2088
+ const marker = /^ {0,3}(`{3,}|~{3,})\s*$/.exec(line)?.[1];
2089
+ return marker !== undefined && marker.startsWith(fence.char) && marker.length >= fence.length;
2039
2090
  }
2040
2091
  /**
2041
2092
  * The `## Summary` section of `.plumbbob/detail.md` and the numbered detail
@@ -2079,8 +2130,7 @@ export function normalizeDetailHeadings(detail) {
2079
2130
  let afterHeading = false;
2080
2131
  for (const line of detail.split('\n')) {
2081
2132
  if (fence !== null) {
2082
- const close = /^ {0,3}(`{3,}|~{3,})\s*$/.exec(line)?.[1];
2083
- if (close !== undefined && close.startsWith(fence.char) && close.length >= fence.length) {
2133
+ if (closesFence(line, fence)) {
2084
2134
  fence = null;
2085
2135
  }
2086
2136
  out.push(line);
@@ -2091,10 +2141,7 @@ export function normalizeDetailHeadings(detail) {
2091
2141
  if ((heading || afterHeading) && line.trim() !== '' && prior !== undefined && prior.trim() !== '') {
2092
2142
  out.push('');
2093
2143
  }
2094
- const open = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
2095
- if (open !== undefined) {
2096
- fence = { char: open.charAt(0), length: open.length };
2097
- }
2144
+ fence = fenceOpening(line);
2098
2145
  out.push(line);
2099
2146
  afterHeading = heading;
2100
2147
  }
@@ -2132,8 +2179,9 @@ export function parseSections(detail) {
2132
2179
  }
2133
2180
  /**
2134
2181
  * The `## Recommendation` section of `.plumbbob/detail.md`: the one or two
2135
- * plain sentences a decision turn ends on, or null when the model wrote none.
2136
- * `handoff` emits it after the card, unfenced; the eye lands on the last text,
2182
+ * plain sentences a decision turn ends on, each shell command they name in a
2183
+ * `sh` fence of its own, or null when the model wrote none. `handoff` emits it
2184
+ * after the card, the prose itself unfenced; the eye lands on the last text,
2137
2185
  * and the last text should say which move the model would take.
2138
2186
  */
2139
2187
  export function parseRecommendation(detail) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plumbbob",
3
- "version": "0.13.5",
3
+ "version": "0.13.6",
4
4
  "description": "Attention-first build process: 15 skills + a CLI that keep you the decider — guidance on the work, a latch on the record.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -181,7 +181,10 @@ switch with `/model` and rerun to honor it, or wave you on. Advisory, never a ga
181
181
  sentence, closed by a period, then the reason as a capitalized sentence or two. The lead
182
182
  and the recommendation are flowing prose and handoff unwraps both to the renderer's
183
183
  width, so never hard-wrap them; handoff also prepends the bold `**Recommendation**:`
184
- label, which you never type. `checkpoint` records this file beneath the step's dated
184
+ label, which you never type. A shell command you want the human to run never rides
185
+ inside a sentence: end the sentence on a colon and give the command a fenced `sh` block
186
+ of its own, one command to a line, which handoff keeps line for line. A slash command
187
+ or a path stays in backticks in the sentence. `checkpoint` records this file beneath the step's dated
185
188
  line in the build-log's `## Log` and then truncates it, so the detail lands in the
186
189
  tracked ledger rather than piling up on disk.
187
190
 
@@ -283,7 +283,10 @@ read checked and found sound. A plan with a hole gets `Sharpen <the one worst ho
283
283
  and why it matters; when the read found more than one, say how many sit behind `expand`
284
284
  and name `/plumbbob:refine` as the move that writes them up as Open questions, with their
285
285
  plain and lean sub-lines, for the human to settle. Three findings at most; the rest is
286
- refine's. `plumbbob handoff --plan` prints the label and your recommendation beneath the
286
+ refine's. A shell command you want the human to run never rides inside a sentence: end the
287
+ sentence on a colon and give the command a fenced `sh` block of its own, one command to a
288
+ line, which handoff keeps line for line; `/plumbbob:refine` stays in backticks in the
289
+ sentence. `plumbbob handoff --plan` prints the label and your recommendation beneath the
287
290
  state table, then the `more details` link to the file, "expand 2" opens `## 2`, and `checkpoint --plan` records the read beneath the
288
291
  plan's line in the build-log's `## Log`, the build's first entry. The cold read is the tip of refine: it makes the
289
292
  full attack discoverable at the moment it would pay, and lets a sound plan skip it. Refine
@@ -196,7 +196,10 @@ A park you leave out reads `no lean proposed` on the card and stays open, and a
196
196
  reads `not given`. The section vanishes when no park is open, and so does the card's Harvested part
197
197
  ([D100 (stop-card)](https://github.com/robmclarty/plumbbob/blob/main/docs/decisions.md#d100)).
198
198
  The recommendation speaks to the plan's stop, the header's `stop:` line, and to the
199
- proposals: the move you would take on both, then why.
199
+ proposals: the move you would take on both, then why. A shell command you want the human to
200
+ run never rides inside a sentence: end the sentence on a colon and give the command a fenced
201
+ `sh` block of its own, one command to a line, which handoff keeps line for line. A slash
202
+ command or a path stays in backticks in the sentence.
200
203
 
201
204
  *Render it and stop.* Run `plumbbob handoff --stop` and paste its output whole, verbatim, at
202
205
  top level, trailing blank line included, then end the turn. Write nothing before it or after
@@ -114,7 +114,10 @@ this skill verifies it the same way: **it reads the diff, not the author**
114
114
  sentence, closed by a period, then the reason as a capitalized sentence or two. The lead
115
115
  and the recommendation are flowing prose and handoff unwraps both to the renderer's
116
116
  width, so never hard-wrap them; handoff also prepends the bold `**Recommendation**:`
117
- label, which you never type. This is the **one** file `/plumbbob:verify` writes, and it
117
+ label, which you never type. A shell command you want the human to run never rides
118
+ inside a sentence: end the sentence on a colon and give the command a fenced `sh` block
119
+ of its own, one command to a line, which handoff keeps line for line. A slash command
120
+ or a path stays in backticks in the sentence. This is the **one** file `/plumbbob:verify` writes, and it
118
121
  is turn presentation, never the diff under review; the author-blind contract below still
119
122
  holds. `checkpoint` records this file beneath the step's dated line in the build-log's
120
123
  `## Log` and then truncates it (the detail plane in the