This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit ee38db59492fc7e79da6efbbf7ab7ad5e25dccb9
Author: Stephen Mallette <[email protected]>
AuthorDate: Sat Oct 10 15:14:31 2026 -0400

    Record bead reasoning in description instead of design
    
    bd can search a bead's description but not its design, so PRIME.md,
    tinker-review, the beads helper scripts and the committer docs now use
    description for reasoning.
    
    Assisted-by: Claude Code:claude-opus-5-5
---
 .beads/PRIME.md                                      | 20 ++++++++++++--------
 .skills/tinker-review/playbooks/general.md           |  8 ++++----
 .skills/tinker-review/references/enrichment-cli.md   |  2 +-
 .skills/tinker-review/references/schema.md           |  4 ++--
 .skills/tinker-review/scripts/discovery/beads.js     |  6 ++----
 .skills/tinker-review/scripts/enrichment/api.js      |  4 ++--
 .../tinker-review/scripts/graph/populate-beads.js    |  1 -
 .skills/tinker-review/scripts/renderer/render.js     |  8 ++++----
 .skills/tinker-review/scripts/renderer/template.html |  2 +-
 .skills/tinker-review/test/beads.test.js             |  5 +++--
 bin/beads-commits.py                                 |  4 ++--
 bin/beads-report.py                                  | 16 ++++++----------
 docs/src/dev/developer/for-committers.asciidoc       |  5 ++---
 13 files changed, 41 insertions(+), 44 deletions(-)

diff --git a/.beads/PRIME.md b/.beads/PRIME.md
index 68fd335cd5..3571cecb51 100644
--- a/.beads/PRIME.md
+++ b/.beads/PRIME.md
@@ -56,20 +56,24 @@ OptionsStrategy" describes code and stays a comment.
 ```bash
 # Only when something was actually ruled out. No fork = implementation; the 
code documents that.
 bd create --type=decision --parent=<root> --title="Chose X" --metadata 
'{"rejected":false}' \
-          --design="why, and what X rules out"
+          --description="why, and what X rules out"
 # An approach you tried and abandoned is the strongest sibling — someone 
already walked it.
 bd create --type=decision --parent=<root> --title="Y" --metadata 
'{"rejected":true}' \
-          --design="what Y concretely was, why it lost, what settled it"
+          --description="what Y concretely was, why it lost, what settled it"
 bd dep add <decision> <alternative> -t related    # never put either of these 
on a task bead
 bd close <decision> <alternative>   # both: a decision is resolved the moment 
you write it
 ```
 
+**Reasoning goes in `--description`, never `--design`.** `bd search 
--desc-contains` and
+`bd query description=` find text in a description; nothing in bd searches 
design, so
+reasoning written there can be read but never found.
+
 **Close a decision as you create it.** Left `open` it lands in `bd ready`, 
advertising
 reasoning as startable work, and its `closed_at` ends up stamped with the 
merge date — dating
 the decision to a day it was not made. It gets pinned with the rest of the 
subtree at merge
 (section 4).
 
-**A rejected alternative's `--design` names three things: what the option 
concretely was, why it
+**A rejected alternative's `--description` names three things: what the option 
concretely was, why it
 lost, and what settled it.** The verdict is already in `rejected`, so the 
reason is the whole
 value — give it as a mechanism ("it leaves the shared database with a 
vocabulary no single
 PRIME.md describes"), never a judgment ("rejected as worse"). A mechanism can 
be checked again
@@ -80,8 +84,8 @@ judgment cannot, so the option stays dead by default. The 
chosen decision needs
 reason is indistinguishable from a recorded one, so a guess does not read as a 
guess three years
 later — it reads as fact and gets trusted. "The operator preferred the other 
one" is the verdict
 again, not a reason. Ask what the chosen option buys and what the rejected one 
would have cost,
-and write that answer. Ask while the conversation is live: section 5 forbids 
editing a design in
-place, so a reason invented now is permanent. Doubt is the trigger — if you 
are reconstructing
+and write that answer. Ask while the conversation is live: section 5 forbids 
editing a decision's
+description in place, so a reason invented now is permanent. Doubt is the 
trigger — if you are reconstructing
 rather than recalling, you are guessing.
 
 **Cite code as `file[sha]`** — `GryoPool.java[bece4a34c7]`, with enough path 
to be unambiguous
@@ -223,9 +227,9 @@ Pinning is what makes a bead permanent — every destructive 
operation keys on
 ## 5. Never
 
 - **Never `bd flatten`, `bd compact`, or `bd admin compact`.** They rewrite or 
discard
-  history irreversibly. `admin compact` destroys `--design` text specifically. 
`bd gc` only
+  history irreversibly. `bd gc` only
   with `--skip-decay`.
-- **Never edit an existing bead's `--design` in place.** Add a comment, or 
create a new
+- **Never edit an existing decision's `--description` in place.** Add a 
comment, or create a new
   decision bead with a `supersedes` edge. Field rewrites are invisible to 
history and lose
   the reasoning that was there.
 - `bd prune` / `bd purge` / `bd gc` are release-time maintainer operations. 
Don't run them.
@@ -342,7 +346,7 @@ bd show <id>                     # one bead with 
dependencies
 bd query "status=open AND type=decision"    # should be empty; an open 
decision was left unclosed
 bd query "metadata.rejected=true" --all     # roads not taken; --all or closed 
beads are hidden
 bd comment <id> "..."            # a fact with no fork in it (never on a task 
bead)
-bd create --type=... --parent=<root> --design=... --labels=...
+bd create --type=... --parent=<root> --description=... --labels=...
 bd dep add <task> <blocker>      # default type is blocks: <task> waits for 
<blocker>
 bd dep add <a> <b> -t caused-by|related|discovered-from|supersedes
 bd dep cycles                    # a plan with a cycle cannot execute
diff --git a/.skills/tinker-review/playbooks/general.md 
b/.skills/tinker-review/playbooks/general.md
index 2387c8a081..60042490a1 100644
--- a/.skills/tinker-review/playbooks/general.md
+++ b/.skills/tinker-review/playbooks/general.md
@@ -24,7 +24,7 @@ confidence pass so the `governs` edges they write go through 
it:
 - `listBeads` — read the roots, decisions (each chosen one with the 
alternatives
   it beat) and tasks. The `primary` root is the PR's own work; `owner` and
   `shared` roots are connected work that explains it.
-- `linkBead` — link each decision whose design is about changed code to that
+- `linkBead` — link each decision whose reasoning is about changed code to that
   Function, Type or File, chosen and rejected alike.
 
 ## Inspect
@@ -46,9 +46,9 @@ confidence pass so the `governs` edges they write go through 
it:
 - Test helpers without guard clauses (missing else/throw for invalid input)
 
 **Project memory** (only when beads were found; read decisions by `listBeads`):
-- Chosen decisions are claims to verify, not reasoning to accept. A design
-  states facts — a mechanism, an invariant, a measurement, what the choice 
rules
-  out. Check each against the code, and against the build or the functional 
test
+- Chosen decisions are claims to verify, not reasoning to accept. A decision's
+  description states facts — a mechanism, an invariant, a measurement, what the
+  choice rules out. Check each against the code, and against the build or the 
functional test
   where the claim is about behavior. Does the diff do something the decision
   rules out? Judge against the newest decision in a `supersededBy` chain.
 - Rejected alternatives — each answers "why not X?" only if the reason X lost
diff --git a/.skills/tinker-review/references/enrichment-cli.md 
b/.skills/tinker-review/references/enrichment-cli.md
index c23a4eaa42..9eabe83a1d 100644
--- a/.skills/tinker-review/references/enrichment-cli.md
+++ b/.skills/tinker-review/references/enrichment-cli.md
@@ -153,7 +153,7 @@ proposal`.
 ### linkBead
 Records that a bead — usually a decision — governs a piece of the changed code,
 as a `governs` edge (Bead → Function, Type or File). Draw it when a decision's
-design is about that code, so Inspect checks the code against what the decision
+reasoning is about that code, so Inspect checks the code against what the 
decision
 chose and ruled out, and the report can cite the decision beside it. Key a
 Function/Type by `--name` (add `--file` when the name repeats) and a File by
 `--file`. Defaults to `INFERRED`: the link is your judgment, not a bd fact.
diff --git a/.skills/tinker-review/references/schema.md 
b/.skills/tinker-review/references/schema.md
index 26fd9de4a1..026df0e9a7 100644
--- a/.skills/tinker-review/references/schema.md
+++ b/.skills/tinker-review/references/schema.md
@@ -111,13 +111,13 @@ A comment on a Discussion.
 
 ### Project memory
 
-**Bead** `{ beadId, type, status, title, description, design, labels, root, 
rejected?, parent?, externalRef?, closedAt?, rootRole? }`
+**Bead** `{ beadId, type, status, title, description, labels, root, rejected?, 
parent?, externalRef?, closedAt?, rootRole? }`
 A bead from the reviewer's local beads database (`bd`), loaded because a record
 bead's external ref names one of the PR's artifacts (see `discovery/beads.js`).
 Every bead in the subtree of each root reached that way is loaded. `type` is
 bd's issue type (`epic`, `feature`, `task`, `bug`, `decision`, `record`, …);
 `root` is the id of the root the bead belongs to. `rejected` is set only on
-decisions — `true` marks the road not taken, and `design` then says what the
+decisions — `true` marks the road not taken, and `description` then says what 
the
 option was, why it lost and what settled it. `externalRef` is set only on
 records. `labels` is the comma-joined label list.
 
diff --git a/.skills/tinker-review/scripts/discovery/beads.js 
b/.skills/tinker-review/scripts/discovery/beads.js
index 9896a1bd52..0e7eee2a1d 100644
--- a/.skills/tinker-review/scripts/discovery/beads.js
+++ b/.skills/tinker-review/scripts/discovery/beads.js
@@ -46,9 +46,8 @@ export const BEADS_REQUIRED_MESSAGE =
  *   expected to be open (it closes at merge), and decisions are closed as 
they are written, so
  *   status means most on tasks: `in_progress` is claimed, unfinished work.
  * @property {string} title
- * @property {string} description
- * @property {string} design - For a decision, the reasoning: why it was 
chosen, or what the rejected
- *   option was, why it lost and what settled it.
+ * @property {string} description - For a decision, the reasoning: why it was 
chosen, or what the
+ *   rejected option was, why it lost and what settled it.
  * @property {boolean|null} rejected - On a decision, true marks the road not 
taken; null on other types.
  * @property {string[]} labels
  * @property {string|null} parent - Parent bead id; null on a root.
@@ -167,7 +166,6 @@ function toBead(raw) {
     status: raw.status,
     title: raw.title || "",
     description: raw.description || "",
-    design: raw.design || "",
     rejected: raw.issue_type === "decision" ? raw.metadata?.rejected === true 
: null,
     labels: raw.labels || [],
     parent: raw.parent || null,
diff --git a/.skills/tinker-review/scripts/enrichment/api.js 
b/.skills/tinker-review/scripts/enrichment/api.js
index 2952ac251c..fc0dc14630 100644
--- a/.skills/tinker-review/scripts/enrichment/api.js
+++ b/.skills/tinker-review/scripts/enrichment/api.js
@@ -220,7 +220,7 @@ export async function listBeads(g, filter = {}) {
       type: one(m, "type"),
       status: one(m, "status"),
       title: one(m, "title"),
-      design: one(m, "design") || "",
+      description: one(m, "description") || "",
       root: one(m, "root"),
       rootRole: one(m, "rootRole") ?? null,
       rejected: one(m, "rejected") ?? null,
@@ -397,7 +397,7 @@ export async function linkDiscussion(g, url, source, title, 
body, confidence) {
 /**
  * Record that a bead — usually a decision — governs a piece of the changed
  * code, as a `governs` edge (Bead → Function, Type or File). Draw it when a
- * decision's design is about that code, so Inspect can check the code against
+ * decision's reasoning is about that code, so Inspect can check the code 
against
  * what the decision chose and ruled out, and the report can cite the decision
  * beside it. Key a Function or Type by `--name` (plus `--file` when the name
  * repeats) and a File by `--file`.
diff --git a/.skills/tinker-review/scripts/graph/populate-beads.js 
b/.skills/tinker-review/scripts/graph/populate-beads.js
index a772c3b972..650603f650 100644
--- a/.skills/tinker-review/scripts/graph/populate-beads.js
+++ b/.skills/tinker-review/scripts/graph/populate-beads.js
@@ -72,7 +72,6 @@ export async function populateBeads(g, beads) {
       .property("status", b.status)
       .property("title", b.title)
       .property("description", (b.description || "").slice(0, 4000))
-      .property("design", (b.design || "").slice(0, 4000))
       .property("labels", (b.labels || []).join(", "))
       .property("root", b.root);
     if (b.rejected !== null) t = t.property("rejected", b.rejected);
diff --git a/.skills/tinker-review/scripts/renderer/render.js 
b/.skills/tinker-review/scripts/renderer/render.js
index aa5dc2440f..012af4ef87 100644
--- a/.skills/tinker-review/scripts/renderer/render.js
+++ b/.skills/tinker-review/scripts/renderer/render.js
@@ -241,7 +241,7 @@ function beadStatus(b) {
 }
 
 function beadText(text) {
-  return text ? `<div class="bead-design">${esc(text)}</div>` : "";
+  return text ? `<div class="bead-reasoning">${esc(text)}</div>` : "";
 }
 
 function renderDecisions(decisions, beadsById) {
@@ -271,11 +271,11 @@ function renderDecisions(decisions, beadsById) {
 
   const items = decisions.filter((d) => d.rejected === false).map((d) => {
     const alts = (rejectedOf.get(d.id) || []).map((a) =>
-      `<li>${beadAnchor(a, `<span class="bead-rejected">Instead of</span> 
${esc(a.title)} 
<code>${esc(a.id)}</code>${superseded(a)}`)}${beadText(a.design)}</li>`).join("");
-    return `<li>${beadAnchor(d, `<strong>Chose</strong> ${esc(d.title)} 
<code>${esc(d.id)}</code>${superseded(d)}`)}${beadText(d.design)}${alts ? `<ul 
class="bead-alternatives">${alts}</ul>` : ""}</li>`;
+      `<li>${beadAnchor(a, `<span class="bead-rejected">Instead of</span> 
${esc(a.title)} 
<code>${esc(a.id)}</code>${superseded(a)}`)}${beadText(a.description)}</li>`).join("");
+    return `<li>${beadAnchor(d, `<strong>Chose</strong> ${esc(d.title)} 
<code>${esc(d.id)}</code>${superseded(d)}`)}${beadText(d.description)}${alts ? 
`<ul class="bead-alternatives">${alts}</ul>` : ""}</li>`;
   });
   const unpaired = decisions.filter((d) => d.rejected === true && 
!paired.has(d.id)).map((d) =>
-    `<li>${beadAnchor(d, `<span class="bead-rejected">Not taken</span> 
${esc(d.title)} 
<code>${esc(d.id)}</code>${superseded(d)}`)}${beadText(d.design)}</li>`);
+    `<li>${beadAnchor(d, `<span class="bead-rejected">Not taken</span> 
${esc(d.title)} 
<code>${esc(d.id)}</code>${superseded(d)}`)}${beadText(d.description)}</li>`);
   const chosen = decisions.filter((d) => d.rejected === false).length;
   return `<details class="bead-decisions">
       <summary>Decisions: ${chosen} chosen, ${decisions.length - chosen} not 
taken</summary>
diff --git a/.skills/tinker-review/scripts/renderer/template.html 
b/.skills/tinker-review/scripts/renderer/template.html
index 8cdfe37c75..9b7ad60a60 100644
--- a/.skills/tinker-review/scripts/renderer/template.html
+++ b/.skills/tinker-review/scripts/renderer/template.html
@@ -60,7 +60,7 @@ pre code { background: none; padding: 0; }
 .bead-ref { border-color: #6f42c1; color: #6f42c1; } .bead-ref::after { 
content: " \2193"; } .bead-ref:hover { background: #6f42c1; color: #fff; }
 .bead-list { margin: 0.4rem 0 0.4rem 1.2rem; padding: 0; font-size: 0.875rem; 
} .bead-list li { margin-bottom: 0.45rem; }
 .bead-alternatives { margin: 0.3rem 0 0 1.2rem; padding: 0; list-style: none; 
border-left: 2px solid var(--border); padding-left: 0.6rem; }
-.bead-design { font-size: 0.8rem; color: var(--text-muted); white-space: 
pre-wrap; margin-top: 0.15rem; }
+.bead-reasoning { font-size: 0.8rem; color: var(--text-muted); white-space: 
pre-wrap; margin-top: 0.15rem; }
 .bead-rejected { color: var(--danger); font-weight: 600; } .bead-superseded { 
font-size: 0.75rem; color: var(--text-muted); font-style: italic; }
 .bead-status { font-size: 0.7rem; text-transform: uppercase; color: 
var(--text-muted); }
 .bead-decisions summary, .card details summary { cursor: pointer; font-weight: 
600; font-size: 0.9rem; margin-top: 0.4rem; }
diff --git a/.skills/tinker-review/test/beads.test.js 
b/.skills/tinker-review/test/beads.test.js
index 7e0dbae873..6e3094c7ac 100644
--- a/.skills/tinker-review/test/beads.test.js
+++ b/.skills/tinker-review/test/beads.test.js
@@ -34,7 +34,7 @@ import { render } from "../scripts/renderer/render.js";
 function bead(id, type, { parent = null, ref = null, rejected, deps = [], 
comments = 0, title = id } = {}) {
   return {
     id, issue_type: type, status: type === "decision" ? "closed" : "open", 
title,
-    description: "", design: `design of ${id}`, labels: ["gremlin-core"], 
parent,
+    description: `reasoning of ${id}`, labels: ["gremlin-core"], parent,
     external_ref: ref, closed_at: null, comment_count: comments,
     metadata: rejected === undefined ? null : { rejected },
     dependencies: [
@@ -216,7 +216,7 @@ const meta = { pr: 99, headSha: "abc", title: "t", domains: 
[], timestamp: "now"
 
 function shaped(id, type, extra = {}) {
   return {
-    id, type, status: "closed", title: `title ${id}`, description: "", design: 
`why ${id}`,
+    id, type, status: "closed", title: `title ${id}`, description: `why ${id}`,
     rejected: type === "decision" ? false : null, labels: [], parent: null, 
externalRef: null,
     closedAt: null, root: "tp-a", edges: [], ...extra,
   };
@@ -249,6 +249,7 @@ test("Project Memory shows the root, its records, plan and 
decisions with altern
   assert.match(html, /Unanswered questions:[\s\S]*tp-a\.7/);
   assert.match(html, /Decisions: 2 chosen, 1 not taken/);
   assert.match(html, /Chose<\/strong> title tp-a\.2[\s\S]*superseded 
by[\s\S]*bead-alternatives[\s\S]*Instead of<\/span> title tp-a\.3/);
+  assert.match(html, /<div class="bead-reasoning">why 
tp-a\.2<\/div>[\s\S]*<div class="bead-reasoning">why tp-a\.3<\/div>/, "a 
decision's description is its reasoning");
   assert.match(html, /a fact on the root/);
   assert.match(html, /<h3>Beads<\/h3>/, "the appendix lists the loaded beads");
   for (const b of found.beads) {
diff --git a/bin/beads-commits.py b/bin/beads-commits.py
index b226480b3d..d74684714e 100755
--- a/bin/beads-commits.py
+++ b/bin/beads-commits.py
@@ -39,9 +39,9 @@ there to write its record. Recording the landing is the one 
step every root
 passes through, and by then GitHub knows which PR carried the commits. A PR
 recorded under another root is related to this one rather than duplicated.
 
-The shas go in `notes` because that is the one free-text field bd can query.
+The shas go in `notes` because that is a free-text field bd can query.
 `bd search` matches title, id and some external refs but never notes, and
-`bd query` has no field for comments or design, so a sha recorded anywhere else
+`bd query` has no field for comments, so a sha recorded in one
 is unreachable except by grepping an export. `bd query 'notes=...'` is a
 substring match, which is why the full 40-character sha is stored: a short sha
 pasted from `git log --oneline` is a prefix of it, so both forms of the query 
find the bead. Quote the sha inside the
diff --git a/bin/beads-report.py b/bin/beads-report.py
index 343ee2e010..720e790193 100755
--- a/bin/beads-report.py
+++ b/bin/beads-report.py
@@ -45,7 +45,8 @@ Checks divide into two kinds, and the distinction matters:
               "decisions were not captured", so they are questions for a
               human, never verdicts.
 
-Neither kind can judge whether design text is real reasoning or fluent filler.
+Neither kind can judge whether a decision's description is real reasoning or
+fluent filler.
 Structure is checkable; substance is not.
 """
 
@@ -79,7 +80,7 @@ def as_list(payload):
 
 
 def load_export():
-    """Whole database, one record per line. Carries design and comments."""
+    """Whole database, one record per line. Carries comments."""
     proc = subprocess.run(["bd", "export"], capture_output=True, text=True)
     if proc.returncode != 0:
         sys.exit(f"bd export failed: {proc.stderr.strip()}")
@@ -99,10 +100,7 @@ def load_subtree(root):
 
     `bd children` recurses in its text output but NOT under `--json`, which
     returns direct children only -- so a single call silently truncates the
-    subtree to depth 1. Verified against a four-level tree at bd 1.1.2.
-
-    The records returned carry no `design` field, only comment_count, so
-    rationale-at-risk detection is weaker in this scope than at release 
scope."""
+    subtree to depth 1. Verified against a four-level tree at bd 1.1.2."""
     beads = {}
     for record in as_list(bd("show", root)):
         beads.setdefault(record["id"], record)
@@ -165,13 +163,11 @@ def main():
         if bead.get("comment_count") or bead.get("comments"):
             n = bead.get("comment_count") or len(bead.get("comments") or [])
             carried.append(f"{n} comment(s)")
-        if (bead.get("design") or "").strip():
-            carried.append("design text")
+        if bead.get("issue_type") == "decision" and (bead.get("description") 
or "").strip():
+            carried.append("reasoning")
         if carried:
             at_risk.append(f"{bead['id']:18} {', '.join(carried):22} 
{bead['title'][:44]}")
     report("Rationale on closed beads (lost at next purge -- pin to keep)", 
at_risk)
-    if scoped:
-        print("  note: subtree scope cannot see `design` text; comment counts 
only")
 
     # --- HEURISTIC ---------------------------------------------------------
 
diff --git a/docs/src/dev/developer/for-committers.asciidoc 
b/docs/src/dev/developer/for-committers.asciidoc
index a56b5040f3..c0f813762f 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -290,9 +290,8 @@ landed there is refused rather than recorded, because it is 
precisely the hash t
 squash would invalidate. Running the command a second time finds the existing 
record and creates
 nothing.
 
-The notes field is what makes the link work in reverse. It is the only 
free-text field that the
-query language searches, so a hash held in a comment or in design text can be 
read but never
-found.
+The notes field is what makes the link work in reverse. The query language 
searches it, so a
+hash held there can be found, while one held in a comment can be read but 
never found.
 
 [source,text]
 ----

Reply via email to