WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
This commit is contained in:
+709
@@ -0,0 +1,709 @@
|
||||
# WillTools
|
||||
|
||||
This is the document to read before opening a file here. It assumes you know precast and
|
||||
Python and nothing about this codebase. Four parts, in the order they matter: **what the
|
||||
tool is**, **how it is built**, **the discipline**, **where everything stands**.
|
||||
|
||||
Read part 3 properly. Parts 1 and 2 orient you; part 3 is what stops you doing damage.
|
||||
Every rule in it was bought with a failure and carries the incident that bought it,
|
||||
because a rule whose reason has been lost gets "simplified" away by the next reader.
|
||||
|
||||
One standing convention, and it is the project's premise: **this repository does not state
|
||||
things it has not checked.** Anything unverified is marked unverified; anything measured
|
||||
says when. Where a number rots — line counts, test counts, timings — you get the command
|
||||
instead, because those have each been wrong more than once in a single week. Where a
|
||||
document and the code disagree, **the code wins**; §4.6 lists the disagreements known on
|
||||
2026-08-30.
|
||||
|
||||
---
|
||||
|
||||
## 1. What it is
|
||||
|
||||
### 1.1 The product
|
||||
|
||||
WillTools is one engineer's QC instrument, not a plant production system. The owner is
|
||||
an **independent precast specialty consultant** — not the plant's employee — and on any
|
||||
job his responsibility is **cladding and wall panels only**. Other engineers hold the
|
||||
beams, slabs and columns, and the plant knowledge every check runs on was validated
|
||||
against panels and nothing else.
|
||||
|
||||
`lib/vibe/scope.py` exists because of that, and insists that **out of scope is a third
|
||||
state** — not a pass, not a failure, not an absence. Every report names how much work it
|
||||
left alone and of what kind. The scope is *declared* in the job knowledge layer
|
||||
(`scope.mark_prefixes`), never inferred. The project already paid for the alternative:
|
||||
one unauthorised exclusion hid forty tickets and every check that ran came back clean.
|
||||
|
||||
That is the first face of the rule the whole tree is organised around — **silence is
|
||||
never a pass**. Its full statement is §3.1, and it is the single most important idea
|
||||
here.
|
||||
|
||||
### 1.2 The ribbon
|
||||
|
||||
One tab, four panels: **Tools**, **Setup**, **Design**, **Lab**. Folder names *are* the
|
||||
UI, and `layout:` in a container's `bundle.yaml` decides what gets built at all — a child
|
||||
not listed is absent, not misordered. No button carries hover text (no `tooltip:`, no
|
||||
`author:`, no module docstring); `tests/test_button_tooltips.py` fails by name if one
|
||||
returns.
|
||||
|
||||
**Reinforcing Suggestion** asks which already-reinforced panels, *between them*, account
|
||||
for every feature of this one — a set-cover jigsaw, not a similarity ranking — capped at
|
||||
four donors, with the shortfall named rather than relaxed to a fifth. Search in
|
||||
`reinforcematch`, index in `reinforceindex`/`reinforcecore`, join in `reinforcebind`,
|
||||
wording in `reinforcereport`. **It opens no Transaction, by hard rule**: a wall panel's
|
||||
middle vertical band is per-design engineering, and copying a donor's cage in
|
||||
automatically would be the most dangerous thing this repo could do. An ordinary press
|
||||
rebuilds only pieces whose cheap signature moved; shift-click forces a full rebuild,
|
||||
because the signature cannot see an opening moved or resized in place.
|
||||
|
||||
**Panel Lift Check** is a PCI Chapter 8 handling check over stripping, transportation and
|
||||
erection. The piece is measured from the model, then **you click the inserts carrying each
|
||||
stage, on the piece itself** — the rules supply highlighted defaults and no longer get the
|
||||
last word. It refuses rather than guesses: fewer than two support lines, a stage naming an
|
||||
insert the panel has not got, a stage with no multiplier and no `pci_case`, short/long
|
||||
above 0.75 (two-way spanning, which a beam idealisation cannot represent). Multi-select
|
||||
runs a background batch whose worker lives in `vibe/batchrun.py` and never in the button —
|
||||
a thread reading the command module's scope after pyRevit tears it down raises on a
|
||||
non-main thread and kills Revit. Batches skip the position search — 93% of a run, 12.6 s
|
||||
of a 13.6 s median piece on the reference sweep.
|
||||
|
||||
**Ticket QC** is the flagship. It checks every shop ticket of a design against the
|
||||
extracted spec plus the knowledge layers: concrete strengths, jacking force, strand
|
||||
count/grade/diameter/position, W.W.F., unit weight, embeds in both directions, bar marks,
|
||||
note views, size envelope, interference. **27 check functions** in `ticketchecks.CHECKS`
|
||||
feed **34 numbered checklist rows** in `ticketpage.CHECKLIST_ITEMS` — both counted
|
||||
2026-08-30, both countable with `len()`. Every check is arithmetic; the one exception is
|
||||
quarantined (§3.1). A row's status comes from `run_all`'s disposition log, never from an
|
||||
absence of findings. And a ticket is not one sheet: it is the union of every sheet named
|
||||
for the mark.
|
||||
|
||||
**Snapshot & Close** is the daily close-out. The model is 300+ MB on disk and 10+ GB open,
|
||||
so there is never a second copy in memory: phase A indexes the live document, phase B
|
||||
closes Revit (the press *is* the authorisation), phase C optionally reopens it detached in
|
||||
a deferred host process once the memory is free. The request is journalled before the close
|
||||
and the deferred process journals its outcome, so a day it silently did not run is reported
|
||||
in capitals on the next press.
|
||||
|
||||
Behind the **Config** pulldown on the Setup panel: *Project Config*, *Design Extract*
|
||||
(scrapes a design packet into a reviewable `spec.json`, with whatever the parser could not
|
||||
place landing verbatim in `unresolved` rather than dropped), *Self Check*, *Job Recon*
|
||||
(reconciles the issued ticket set against the whole job — the question no per-ticket check
|
||||
can answer, because a ticket that is not there cannot be checked) and *Takeoff*.
|
||||
|
||||
The **Design** panel is a quarantine: everything on it sits behind the `DesignHub`
|
||||
pulldown, on a panel of its own, so nothing half-built ever sits beside a finished tool.
|
||||
On the **Lab** panel, **GOD_MOD** opens a terminal running `claude` at the repo root with
|
||||
default permissions.
|
||||
|
||||
### 1.3 What a run produces
|
||||
|
||||
**Ticket QC.** The checklist table *is* the page — first, unfolded, one per ticket that
|
||||
has anything to say, in loudness order. `ticketpage.state_color`: OK green; WARN and FOR
|
||||
ENGINEER yellow; **NOT RUN and FAIL both red**. An all-green ticket gets one line, not a
|
||||
table.
|
||||
|
||||
Everything else is a `foldreport.Section`, and the folding is a contract: *the answer is
|
||||
the headline and is never folded; everything else starts closed; a section carrying a
|
||||
problem opens itself, and its `problem` is a **sentence** naming what is wrong, not a
|
||||
boolean; folded is not gone — a closed header still says what is inside and how much.*
|
||||
The reader's side of that contract is **if nothing below the tables is open, nothing needs
|
||||
me.** A problem section renders open as plain HTML needing no script, because pyRevit's
|
||||
output panel is a WinForms `WebBrowser` in IE7 document mode where `<details>` does not
|
||||
exist and fails *silently*.
|
||||
|
||||
Three files land in `_ticketqc/` on every press: `last-ticketqc.txt` (the whole report,
|
||||
nothing folded, long notes wrapped rather than cut, so a dead fold costs a file open and
|
||||
never a sentence); `qc-run.json` (`findings` and `model_read` as two keys, never one list,
|
||||
each entry carrying `source` and a boolean `computed`; muting never touches it, because a
|
||||
muted row is a rendering decision and a muted check that failed still fails the ticket —
|
||||
but it does not yet carry the run log, §4.4); and the `ticketqc/2` evidence file of §2.2
|
||||
(`qc-run.json` itself carries `ticketvision.RUN_SCHEMA = 'qc-run/3'` — read the constants,
|
||||
both have moved since this paragraph was written).
|
||||
|
||||
**Panel Lift Check** produces a self-contained HTML file with the `.job.json` beside it as
|
||||
the complete input, re-runnable where there is no Revit: a stage table, an "Inserts picked"
|
||||
table recording the one part of the run that came from a human, recommended moves in dx/dy
|
||||
inches, and inline SVG free-body, shear, moment and stress plots that load **nothing
|
||||
external**. Two habits in it are worth copying. A stage that was not analysed prints
|
||||
*words*, never a dash, because a dash among numbers reads as a small number. And the
|
||||
station tables under each plot thin ~2000 stations by stride but never thin what matters —
|
||||
both ends, every opening edge, every pick or dunnage line, the peak and trough of V and M,
|
||||
the worst compression, and the governing tension station, tinted and bold and saying so.
|
||||
|
||||
Read Δx as "about here", not as a dimension, until §4.4's solver work is done.
|
||||
|
||||
---
|
||||
|
||||
## 2. How it is built
|
||||
|
||||
### 2.1 Two Pythons, and the line between them
|
||||
|
||||
`WillTools.extension/` is **IronPython 2.7.12** on Revit's UI thread: no f-strings, no
|
||||
`pathlib`, no `dataclasses`, no typing syntax. `panellift/` and `tools/` are **host
|
||||
CPython** (`panellift` declares `>=3.9`).
|
||||
|
||||
**`panellift` is deliberately unimportable from Revit** — it uses dataclasses and typing
|
||||
syntax on purpose. A calculation whose output reaches a plant must be verifiable, and
|
||||
verifiable means a suite that needs no licence, no model and no Windows. That is the
|
||||
whole reason the line is where it is.
|
||||
|
||||
`vibe/liftexport.py` is the entire Revit side of it: it measures the piece, writes the
|
||||
job file, shells out with a timeout and tick callback (`find_python`, `run`), and reads
|
||||
the summary back. **Mechanics live on the far side of that line; facts about *this
|
||||
model* live on the near side.**
|
||||
|
||||
### 2.2 The contract is the file, not the function
|
||||
|
||||
Modules hand each other versioned JSON carrying `schema`, `written_at`, `written_by`,
|
||||
`doc_key` and `knowledge_sources` — not objects. Four instances today:
|
||||
`liftexport.SCHEMA = 'panellift/1'`, `reinforcecore.SCHEMA = 6`,
|
||||
`specformat.SCHEMA_VERSION = 1`, and `ticketqc.SCHEMA = 'ticketqc/2'` (bumped from `/1` on
|
||||
2026-08-31; read the constant, it will move again), written to
|
||||
`<model>/_ticketqc/evidence/run-<stamp>.json` on every Ticket QC press. **Nothing reads
|
||||
the last one yet** — that is step 2 of the migration (§4.5), and precisely why the writer
|
||||
could ship alone. The four rules, from `docs/architecture-migration.md` §2:
|
||||
|
||||
1. **Discard whole on mismatch.** `reinforcecore.load()` and `ticketqc.load_evidence()`
|
||||
throw a stale file away entirely — never partially, never migrated.
|
||||
2. **No live objects.** The plan named two holdouts; the build found four (`t['doc']`,
|
||||
`t['legends']`, `t['sheets']`/`t['sheet']`, and live `XYZ` in `t['strand_points']` on
|
||||
the no-framing early return) — so the serialiser gates on **type**, not on a list of
|
||||
known offenders.
|
||||
3. **Absence is not a value.** A gather that could not read something writes an explicit
|
||||
`null` *and* a reason, through one `put()`. A merely missing key reads downstream as
|
||||
"nothing to check" — the same failure mode `ticketchecks._SKIP_PROBES` exists to
|
||||
prevent (§3.1).
|
||||
4. **Provenance travels** — which layers were in force, which spec source (`spec` or
|
||||
`regex`), which build. `intent` and `spec_source` ride with each ticket. Findings do
|
||||
not: `qc-run.json` owns verdicts and the evidence file owns inputs, and step 2 needs
|
||||
them apart so it can compare one against the other.
|
||||
|
||||
### 2.3 The five knowledge layers
|
||||
|
||||
Each layer overrides the one before it. `knowledge.load(doc)` merges them;
|
||||
`knowledge.sources(doc)` lists them, and Self Check prints them.
|
||||
|
||||
| layer | where it lives |
|
||||
| --- | --- |
|
||||
| `knowledge.DEFAULTS` | this repo — what the code assumes |
|
||||
| `knowledge/catalogue.yaml` | this repo — published hardware ratings, identifies nobody |
|
||||
| plant `knowledge.yaml` | `%APPDATA%\WillTools\` or `WILLTOOLS_KNOWLEDGE`, per-plant folder |
|
||||
| job `knowledge.yaml` | `<model folder>/_ticketqc/` |
|
||||
| the project config file | wherever the engineer put it; `_ticketqc/active-config.txt` points at it |
|
||||
|
||||
**Maps deep-merge, lists replace.** Two consequences bite: a list in the job file
|
||||
*removes* the plant's entries rather than adding to them, and `_deep_update` simply
|
||||
assigns, so **an override and a typo are indistinguishable today**.
|
||||
|
||||
Two refusals guard the edges. A plant named in `job.plant` whose file is missing sets
|
||||
`_plant_missing` rather than silently falling back to the machine-wide file; a pointer to
|
||||
a vanished config sets `_config_missing`. `knowledge.config_problems()` turns either into
|
||||
a sentence the buttons refuse on.
|
||||
|
||||
Note the deliberate two-pass in `load()`: `job.plant` chooses the plant file but lives in
|
||||
a layer that merges *after* it, so `plant_id()` reads the job and config files once,
|
||||
alone, first. **Delete that pre-pass and the tool reads the wrong plant while every report
|
||||
looks normal.**
|
||||
|
||||
Which layer a fact belongs in is not a preference; it is an argument, and it is part of
|
||||
the discipline — §3.2.
|
||||
|
||||
### 2.4 The bridge
|
||||
|
||||
`tools/rvt.py` is a host client for a pyRevit routes endpoint registered by `startup.py`.
|
||||
`rvt.py exec` runs a snippet *inside* the live session and returns
|
||||
`{ok, stdout, result, error, traceback}`. It is how anything is prototyped against a real
|
||||
model, and it is the most dangerous thing in the repo. Its one absolute rule is §3.5.
|
||||
|
||||
### 2.5 Two repos
|
||||
|
||||
This repository can be shown to anybody. The other one cannot. `knowledge/plant/`,
|
||||
`knowledge/job/` and `knowledge/_archive/` sit in this working tree for convenience and
|
||||
are tracked by the **other** repository, never by this one; `.gitignore` says so at the
|
||||
point of exclusion. `knowledge/catalogue.yaml` is the one deliberate exception, being
|
||||
published manufacturer data, and it says so in its own header. The gate that keeps the
|
||||
split honest is §3.4.
|
||||
|
||||
---
|
||||
|
||||
## 3. The discipline
|
||||
|
||||
### 3.1 The honesty invariants
|
||||
|
||||
**NOT RUN is never a pass. Silence that looks like success is the cardinal sin.** NOT RUN
|
||||
renders **red**, with FAIL, and `ticketpage.py` carries a comment forbidding the next
|
||||
reader from greying it out: a check that did not run tells you nothing about the piece, so
|
||||
it must never look calmer than one that failed. One unauthorised exclusion hid forty
|
||||
tickets and every check that ran came back clean (`scope.py`, §1.1); seven `die()` calls
|
||||
let refusals leave through a dialog, so a batch where every panel refused produced a blank
|
||||
report. Every refusal now goes through `not_run()`.
|
||||
|
||||
**`None` ("could not judge") is not `[]` ("judged, found nothing").** `ticketchecks.run_all`
|
||||
disambiguates an empty return with a **skip probe**: `_SKIP_PROBES[check_name]` gives
|
||||
either a reason or `None` (ran, found nothing). A check with *no* probe logs `RAN-EMPTY`
|
||||
and renders NOT RUN reading "no probe declared" — unexplained silence is the defect, not
|
||||
agreement. A probe's sentence must name a verifiable place: row 5.4 now separates *no
|
||||
spec*, *a spec predating the field*, and *a design recording no mesh*. Every registered
|
||||
check has a probe now, and it is test-enforced (§4.4) rather than a count to keep
|
||||
re-auditing by hand.
|
||||
|
||||
**A declared out-of-scope row is a third state, not a silence.** The scope is declared in
|
||||
the job layer, never inferred. No declaration means judge everything, and a mark whose
|
||||
prefix cannot be read is never set aside — "I could not tell" may only resolve towards
|
||||
judging.
|
||||
|
||||
**A model-read (`AI-*`) finding is always WARN**, never OK and never FAIL. With
|
||||
`vision_escalation` on, grade-D items no comparison can settle go to a small model reading
|
||||
one rendered view; the answer lands in `model_read`, never in `findings`, and **can never
|
||||
set a checklist row**. `ticketvision` carries that provenance four independent ways (the
|
||||
`source` field, an `AI-` code owning no row, its own list, its own report section), on the
|
||||
assumption that an edit will drop one and the other three still have to hold. A model that
|
||||
saw nothing wrong in one view is a reason to look, not to stop looking.
|
||||
|
||||
**Muting never silences a FAIL**, and a muted check still *runs*: `apply_muting` repaints
|
||||
it grey rather than removing it, and MUTED gets its own tally bucket. "31 OK" when four of
|
||||
them were nobody's judgement is the quiet lie the table exists to stop.
|
||||
|
||||
**A check may not claim what the tool is in no position to claim.** Rows 5.6 and 5.7 both
|
||||
printed *"no reinforcing found on this piece"*; this plant models bars outside any rebar
|
||||
category, so with `drawings.reinforcing_families` empty a **301-member** piece produced the
|
||||
same words as a piece with no steel in it. They now say the tool could not **recognise**
|
||||
any, name the key that would turn it on, and **stay red** — *a better sentence is not
|
||||
permission to soften the state*. Two siblings landed the same day: 5.1 printed OK off one
|
||||
of its two questions, so a check slot now takes a tuple and **any quiet half makes the row
|
||||
NOT RUN**; and 5.3, a person's job by design, printed red because a hand-kept
|
||||
`FOR ENGINEER` tuple omitted it — that list is now derived from the table.
|
||||
|
||||
### 3.2 The knowledge layer law
|
||||
|
||||
The mechanism is §2.3. The law is this: **job beats plant beats what everybody in the
|
||||
trade knows** — not a tie-break, but a statement about where truth lives. The trade knows
|
||||
a bar mark is a recipe; the plant knows its own hardware; the job knows what was drawn.
|
||||
So **placement is an argument, not a preference**. Three examples from a single day:
|
||||
|
||||
- **Rebar stock lengths → the plant.** One number for every size let a 30 ft `#4` pass
|
||||
where the plant stocks `#4` in 20 ft — a false OK on a real defect class. It became a
|
||||
**map keyed by printed size, in integer inches** (`catalogue.rebar.stock_by_size`, values
|
||||
supplied by the plant layer). As a *list*, a job adding one size would silently delete
|
||||
the plant's whole table.
|
||||
- **A job's odd-grade bar → the job.** The manual files every stock length under a
|
||||
designation the owner says is retired; if it reappears it is an exception to this job,
|
||||
so it is job knowledge.
|
||||
- **Which end is up → public.** The plant layer carried `top_is_higher_global_z`, and both
|
||||
obvious repairs were wrong. An assembly origin is placed when the assembly is made, and
|
||||
**placing it is the statement**: that is how precast is built, not a house habit, so it
|
||||
belongs in the repo catalogue. A key asking a plant to declare it is not an unread key,
|
||||
it is the wrong question — and it named a *mechanism* rather than an answer, which a
|
||||
layer must never be able to swap.
|
||||
|
||||
**Dormant and loud.** A key not yet wired ships **null**, with `*_verified: false` and a
|
||||
written reason — never a guess. The reinforcing rule shipped that way and proved the right
|
||||
*shape* around the wrong *field*; the flag did its job, because the rule never ran and the
|
||||
report named the reason, where a guess would have left every bar in the job silently
|
||||
un-recognised. Turning it on was two YAML fields and no code change. §4.3 lists what is
|
||||
dormant now.
|
||||
|
||||
### 3.3 Never guess a plant fact
|
||||
|
||||
Parameter names, categories, family tokens and casting conventions are not code's to know.
|
||||
They are reached through **roles** declared in the knowledge layers (`PARAMETER_ROLES`,
|
||||
`knowledge.param_names`). Two incidents from one day:
|
||||
|
||||
- **Three guesses, three wrong.** An agent needed one plant parameter name and guessed it
|
||||
three ways; none of the three existed in the model. `rvt.py params <id>` answers it in
|
||||
one request. The precedent: the mark parameter name left the code entirely and became
|
||||
the role `member.mark`.
|
||||
- **Types read where the values were on instances.** A census of the reinforcing parameter
|
||||
read family *types* only, but this plant writes that value on the **instance** — so the
|
||||
census saw nothing and the agent told the owner the wall panels carried no reinforcing
|
||||
at all. Measured properly, one piece had 112 bars and another 84. The rule now carries
|
||||
`read_from: [instance, type]`: the value sits on one or the other, mutually exclusively
|
||||
per element, and a reader taking only one finds nothing on half the model **and returns
|
||||
a result that looks perfectly clean**.
|
||||
|
||||
**Measure it, ask the owner, or stand down with a written reason — never assume.** When a
|
||||
bar-size parameter could not be measured, nobody guessed; it is still an open question
|
||||
(§4.3). *"Ask the owner" is the third resort, not the second:* **search the transcripts
|
||||
first** — §3.8, one-liner in `CLAUDE.md` §7.
|
||||
|
||||
### 3.4 Secrecy and the scrub gate
|
||||
|
||||
**No client, job or plant information anywhere** — code, comments, docstrings, commit
|
||||
messages, examples. Where you need to refer to one, write the `CLAUDE.md` §3 placeholder
|
||||
rather than deleting the sentence. Product codes are not identities and stay as they are.
|
||||
|
||||
`tools/scrub.py` is the gate, in two modes on purpose. `--worktree` is the audit: every
|
||||
file git can see, tracked *and* untracked, and a hit anywhere is a hit. `--staged` is the
|
||||
pre-commit hook: only staged files can block a commit, while untracked hits print loudly
|
||||
without stopping unrelated work — because a gate that blocks unrelated work is one people
|
||||
learn to pass with `--no-verify`. It also runs inside `tools/package.py`. The term list
|
||||
lives in gitignored `.scrub-terms`, so the list of names is never itself the leak;
|
||||
structural patterns (model filenames, absolute user paths) are checked whether or not
|
||||
anyone remembered to add a term.
|
||||
|
||||
**The cautionary tale (2026-08-30).** The gate built each term as `\b<term>\b`. In regex
|
||||
`_` and digits are word characters, so `\bTerm\b` never matched `PREFIX_TERM` — and
|
||||
`PREFIX_ROLE` was the dominant naming convention here before the roles refactor. **The
|
||||
terms most likely to be a real leak were written in the one shape the gate was blind to.**
|
||||
The fix is letter-only lookarounds, and the sanctioned placeholders are now read out of
|
||||
`CLAUDE.md`'s own table so the gate cannot drift from the rule. The rescan: working tree 0
|
||||
hits → **4**; history 19 hits in 3 commits → **22 in 6**, all six already pushed.
|
||||
**Assume every scan before that fix under-reported.** Editing a file does not remove a
|
||||
name from the commit that introduced it — §4.4 has the decision still outstanding.
|
||||
|
||||
### 3.5 Revit safety
|
||||
|
||||
- **Never reload pyRevit. Restart Revit.** `reload_pyrevit()` tears down the engine that
|
||||
owns the routes handler while the HTTP server still points at it, so the next bridge
|
||||
request runs against a dead engine and takes Revit down — on a non-main thread, where
|
||||
Revit cannot catch it. Measured 2026-08-14: 11 clean requests, one reload, the 12th
|
||||
fatal. It happened twice. Our code never gets to run, so the guard sits outside Revit:
|
||||
`startup.py` appends a line to `.bridge-sessions.log` on every pyRevit load, and `rvt.py`
|
||||
reads that log before *every* request and hard-refuses on two loads under one process id.
|
||||
There is no `/reload` endpoint. If `rvt.py` says the bridge is unsafe, do not work around
|
||||
it and do not curl the endpoint — ask for a restart, which picks up new buttons just as
|
||||
well.
|
||||
- **Never run an unfiltered collector; never guess an API enum.** 2026-08-18: one snippet
|
||||
walked the whole document with `WhereElementIsNotElementType()`, touched `.Symbol` on
|
||||
every result, and set `Options.DetailLevel` from a value reached by `__import__`. Revit
|
||||
died with no exception and no journal entry, because **a bad value handed to a native
|
||||
API is not a Python error**. Scope every collector; import enums by name, so a bad name
|
||||
is a traceback rather than a dead process.
|
||||
- **Transactions for writes; reads never need one** (`with tx('name')`). The production
|
||||
model is read-only to agents: prototype read-only, show counts and a sample, and get an
|
||||
explicit go-ahead before any mutation. **Nothing in the extension writes document data or
|
||||
geometry, on purpose** — a wrong report can be disbelieved, but wrong geometry gets built.
|
||||
The one exception is view state: Panel Lift Check opens a `tx('Show assembly')` to
|
||||
isolate the piece temporarily and put an 85% transparency override on its non-insert
|
||||
members, and writes both back off when it is done.
|
||||
|
||||
### 3.6 How the work is organised
|
||||
|
||||
The owner works through sub-agents. **One dedicated long-lived agent holds the bridge**,
|
||||
and all live-Revit work routes through it. Everything else is split into **non-overlapping
|
||||
file sets** and run in parallel, each landing committed **by explicit path** and pushed.
|
||||
|
||||
**`git add -A` is prohibited**, and it has cost twice. `cb1587d` swept in a
|
||||
**deliberately broken** button that the tooltip agent had planted to prove its guard
|
||||
fires, so HEAD briefly shipped a fake tooltip (repair and rule in `14de0d3`). And
|
||||
`109d7df` — titled *"Today is the 30th, not the 31st"* — swept in the first half of
|
||||
another agent's unfinished up-end work; the follow-up, `cf9e164`, opens by saying so.
|
||||
|
||||
A glob is the same mistake in a different coat: `git add .`, `git commit -a`,
|
||||
`git add lib/` while another agent is mid-edit. **Name the files.**
|
||||
|
||||
### 3.7 Testing
|
||||
|
||||
**Host-testable by design.** The whole suite runs on plain CPython with no Revit and no
|
||||
licence:
|
||||
|
||||
```bash
|
||||
python -m pytest WillTools.extension/tests -q # the extension
|
||||
cd panellift && python -m pytest -q # the solver
|
||||
```
|
||||
|
||||
**Run them; do not quote them.** Two readings on 2026-08-30, hours apart on the same
|
||||
machine: `panellift` 272 passed both times, while the extension went from 1807 passed / 1
|
||||
failed to 1860 passed / 1 failed, because another agent was adding tests in between. That
|
||||
is the normal condition here, and it is why every count in this document is date-stamped
|
||||
or replaced by a command. The one thing that held across both readings is the single
|
||||
failure — a real one, environment-coupled, described in §4.4. Several documents in this
|
||||
tree still quote counts that were true when written and are not now (§4.6).
|
||||
|
||||
`tests/conftest.py` is what makes it possible: it registers a bare module object as
|
||||
`sys.modules['vibe']` with `__path__` pointed at the real package, so `vibe/__init__.py` —
|
||||
which imports the Revit API the moment it runs — never executes. That reaches any module
|
||||
touching Revit only *inside* functions needing a live `doc`, and cannot reach one
|
||||
importing the Revit API at **module scope**. `ticketqc.py` / `ticketchecks.py` are the
|
||||
standing example: `ticketqc` imports eight `Autodesk.Revit.DB` names at the top and
|
||||
`ticketchecks` imports `Finding` from it, so freeing them takes more than moving an
|
||||
import. `test_ticketchecks_host.py` reaches a few pure decisions through a deliberately
|
||||
**inert** stub of those names: if an edit makes one really call the API, the test raises
|
||||
instead of quietly passing.
|
||||
|
||||
**Pin against a written-down golden, not a recomputation.** The takeoff count is pinned to
|
||||
numbers typed into the test, because *a pin that recomputes both sides pins nothing* — it
|
||||
agrees with any refactor, including a wrong one.
|
||||
|
||||
**Tombstone tests** keep a deleted thing deleted. `test_no_cap_survives_anywhere_in_the_module`
|
||||
asserts that `ticketpage` has no `capped_note` and no `NOTE_CAP`: that 180-character cap
|
||||
had truncated a debond run mid-number, in both the panel and the text file.
|
||||
|
||||
### 3.8 Search the transcripts before you ask him
|
||||
|
||||
**A question he has already answered is worse than a question nobody asked.** It costs him
|
||||
time, it tells him nobody looked, and — the real damage — it invites him to answer a second
|
||||
time. Two rulings then sit on the record with nothing to say which governs, and the next
|
||||
agent gets to pick. The rule, in his words, 2026-08-31: *"开发纪律里面再加一条,有问题先搜索
|
||||
聊天记录。搜不到再来问我。"*
|
||||
|
||||
**The incident that bought it (2026-08-31).** An overnight audit put
|
||||
`handling.tension_limit_sqrt_fc` at the top of the owner's list as **A0**, under a heading
|
||||
saying it was *"the only open item that changes a live engineering answer"* — reasoning
|
||||
that he had made the stress limit a per-job choice, that the job layer held nothing, and
|
||||
that the job was therefore running on 7.5 inherited from a layer he never said should
|
||||
decide it. Every one of those facts was true. The conclusion was still wrong: he had ruled
|
||||
the value twelve days earlier, **2026-08-19: *"就用7.5sqrt。"*** The plant layer holds
|
||||
exactly 7.5, the tool had been doing the right thing the whole time, and A0 existed only
|
||||
because nobody searched. Escalating it would have spent his morning re-deciding a decided
|
||||
thing.
|
||||
|
||||
Note what the audit actually did wrong, because it is the subtle half: it found a **later**
|
||||
quote (2026-08-30, *"应力极限写入 projectconfig … 让用户自己填写。I can see it both ways"*)
|
||||
and read a ruling about **where the knob lives** as if it vacated the ruling about **what
|
||||
the number is**. Those are different questions and both answers stand. A mechanism ruling
|
||||
does not retract a value ruling — and a search that returns two hits is not finished until
|
||||
you have worked out whether they even disagree.
|
||||
|
||||
**Searching is cheap. `CLAUDE.md` §7 has the one-liner; the measured cost over the whole
|
||||
65 MB of transcripts is 0.2 seconds.** "It is expensive to look" is not available as a
|
||||
reason, and neither is "I did not know where they were".
|
||||
|
||||
**But a search that finds nothing is not proof he never said it.** The transcripts are one
|
||||
machine's history, they are not indexed, they hold his messages in two languages, and the
|
||||
term he used is very often not the term you searched. So the honest report is
|
||||
***"I searched X, Y and Z and found nothing"* — never *"he has never ruled on this"***.
|
||||
State the terms; the next reader can then extend the search instead of repeating it. This
|
||||
is §3.1's invariant wearing different clothes: not-found is a NOT RUN, not a pass.
|
||||
|
||||
When you do find it, **write it down where the value lives** — beside the number, with the
|
||||
date and his words verbatim — so the next agent finds it by reading rather than by
|
||||
searching. A ruling recorded only in a transcript will be asked again.
|
||||
|
||||
### 3.9 Three tiers: the coordinator decides, Sonnet explores, DSV4F labours
|
||||
|
||||
**The coordinating agent (Fable / Opus class) is the most expensive process on this
|
||||
machine, and it spends its budget on four things only: planning and splitting work, judging
|
||||
results, deciding anything irreversible or anything that touches live Revit, and talking to
|
||||
the owner.** Below it sit two cheaper tiers, and they are not interchangeable:
|
||||
|
||||
- **Exploration — reading a codebase to answer "how does this work / what would it take" —
|
||||
goes to a Sonnet sub-agent** (the harness's `Agent` tool with `model: sonnet`; or Opus
|
||||
driving Sonnet), never to DSV4F. Exploration needs judgement about what matters, and the
|
||||
owner's ruling is that DSV4F does not get to make it. Sonnet returns a note with
|
||||
`file:line` citations; the coordinator reads the note, not the files, and goes to the
|
||||
source only for the fact a decision hinges on.
|
||||
- **Labour — extracting tables from documents, mining PDFs into notes and fixtures,
|
||||
translating, classifying, reformatting, and writing code to a settled, pre-split spec —
|
||||
goes to DSV4F (DeepSeek V4 Flash)**, with V4 Pro only where Flash has demonstrably failed
|
||||
on the same material. The owner, 2026-09-05: *"探索你带着sub sonnet或者让opus带sonnet去做。
|
||||
DSV4F别做探索。DSV4F就干点儿体力活儿"*.
|
||||
|
||||
**The incident that bought it (2026-09-05).** `CLAUDE.md` carried *EXPLORE (me) → WRITE
|
||||
(DSV4F) → REVIEW (me)* with the sentence *"exploration never goes to DSV4F"*, and no rule
|
||||
said the exploring had to be cheap. Under that, the coordinator spent an afternoon on
|
||||
`docs/pa-web-spec.md`, `tools/pa_web/static/app.js`, `draw.js` and every key
|
||||
`beamcalc/model.py` reads from a job file — twelve consecutive file reads on the most
|
||||
expensive model, to produce a note a Sonnet sub-agent writes as well. The owner's words:
|
||||
*"用DSV4F。这些东西开发纪律里面没有?"* — and until that day this section did not exist. The
|
||||
first draft of the fix then over-corrected and dispatched that same reconnaissance to DSV4F;
|
||||
he stopped it within minutes with the ruling quoted above. Both halves are the rule: the
|
||||
coordinator does not do the reading, **and** the reading is Sonnet's, not Flash's.
|
||||
|
||||
**Which DSV4F route.** Two exist and they do not share a balance. `cheap` (in `PATH`) is
|
||||
the DeepSeek platform direct; on 2026-09-05 that balance was **negative** (`402 Insufficient
|
||||
Balance`) after the `sample_job` document mining ran V4 Pro at 65k output tokens with parallel retries, and
|
||||
every `cheap` call and mining script died with it. **Command Code (`cmd -p "<brief>" --model
|
||||
deepseek/deepseek-v4-flash --yolo --no-session`) is a separate account with its own quota**,
|
||||
and the owner's instruction that day was to use it. Check the balance before dispatching a
|
||||
batch, not after it has failed; a 402 on the first call means every call.
|
||||
|
||||
**Detach anything that outlives a turn.** The coordinator runs as a systemd service under a
|
||||
watchdog that restarts it — three times on 2026-09-05: 13:11 and 14:11 for a mis-detected
|
||||
gateway, 15:09 for a stall. A service restart kills the whole cgroup, and each time it took
|
||||
the background workers with it: seven `cmd` mining jobs launched from the coordinator's
|
||||
shell, four dead mid-write, output files 0 bytes or absent. A worker launched with
|
||||
**`systemd-run --user --unit <name> …`** is its own transient unit and survives; one
|
||||
launched with `&`, `nohup` or `setsid` from the coordinator's shell does not. Write the
|
||||
worker's result to a file and its status to a log (§3.1 applies: an empty log is a NOT RUN,
|
||||
not a pass), so a coordinator that has lost its memory can read what finished instead of
|
||||
asking.
|
||||
|
||||
**Split before you send, and size to one reply.** A whole-feature brief truncates at
|
||||
max-tokens and reads as "it skipped the hard parts"; one function, one section, one file's
|
||||
change per task (`CLAUDE.md` § delegation, owner 2026-09-03: *"确认了边界的活儿,拆细了再让
|
||||
DS 干"*). The gates — pytest, scrub, review — run on the coordinator's side, never inside the
|
||||
worker.
|
||||
|
||||
---
|
||||
|
||||
## 4. Where it stands
|
||||
|
||||
Written 2026-08-30 against `ff10870`. The working tree was clean when this survey was
|
||||
taken; it is edited concurrently by several agents, so check `git status` yourself rather
|
||||
than trusting that. `docs/checklist-code-map.md` was rebuilt 08-30 17:19 and commits have
|
||||
landed since.
|
||||
|
||||
### 4.1 The checklist, row by row
|
||||
|
||||
Authority is `CHECKLIST_ITEMS` in `lib/vibe/ticketpage.py`.
|
||||
|
||||
| # | grade | state | note |
|
||||
| --- | --- | --- | --- |
|
||||
| 1.1 sheet name = mark | A | running | no skip probe (§4.4) |
|
||||
| 1.2 sheet design reduces to piece design | A | running | judged in `ticketqc.check_ticket`; no named check fn |
|
||||
| 1.3 one ticket set per piece | A | running | no skip probe |
|
||||
| 1.4 view names refer to this design | B | running; **dormant out of the box** | needs `nomenclature.design_number_re` — plant layer, not `DEFAULTS`. Re-graded A→B 08-30 |
|
||||
| 1.5 call-outs do not overlap | C | running, built 08-30 | rotated annotations set aside under `CALLOUT-OVERLAP-UNREAD`; no sample, untested |
|
||||
| 2.1 printed size vs modelled | B | running | |
|
||||
| 2.2 weight three ways | A | running | the *measured* leg needs run option `expensive_geometry` (default ON); off, the row is NOT RUN naming it |
|
||||
| 2.3 unit weight | A | running | |
|
||||
| 2.4 cast extras in the weight | A | running, built 08-30 | verified end-to-end on a real ticket |
|
||||
| 3.1 f'c · 3.2 f'ci | A | running | |
|
||||
| 3.3 release ratio | B | **muted on this job**, still runs | owner's own dated sentence: no panel here has the thing it asks about |
|
||||
| 4.1 strand count · 4.2 diameter & grade | B/A | running | |
|
||||
| 4.3 jacking force | A | running | `tolerances.it_lb` wired 08-30 to **100 lb**; the job layer's `1.0` deleted, not corrected |
|
||||
| 4.4 strand positions vs chain from rail | B | running | stands down with a reason where there is no strand geometry |
|
||||
| 4.5 debond call-out | C | running, built 08-30 | consumes 4.7; where 4.7 could not judge, 4.5 is NOT RUN with 4.7's reason |
|
||||
| 4.6 rows & spacing | A | running | same check as 4.4 |
|
||||
| 4.7 strand debonded where concrete runs short | B | running | |
|
||||
| 5.1 BED PLAN call-out embed exists | A | **half running by construction** | `check_bom_members` runs; `check_detail_callouts` reports NOT RUN *naming the sheets nobody read* wherever the plan cites a bubble. Only a plan citing no detail can be OK |
|
||||
| 5.2 embed in model appears on ticket | A | running | |
|
||||
| 5.3 face designations vs model side | C | **FOR ENGINEER by design** | printed a red NOT RUN on every ticket until 08-30 — missing from a hand-kept tuple (§3.1) |
|
||||
| 5.4 mesh / WWF designation | A | **muted on this job**, still runs | no mesh on this job |
|
||||
| 5.5 bar marks legible | D | **FOR ENGINEER by design** | a property of the picture |
|
||||
| 5.6 bar marks unique | B | running, built 08-30 | split out of the old 5.5; runs where the reinforcing rule resolves (§4.3) |
|
||||
| 5.7 no bar longer than its stock | B | running, **against one generic number** | the per-size stock table is built, populated and unreachable (§4.3) |
|
||||
| 6.1 insert capacity · 6.2 edge distance | A | running | no skip probe |
|
||||
| 6.3 handling result | A | running | no skip probe; three silent `return []` paths |
|
||||
| 6.4 dunnage not under an opening | A | running, probe added 08-30 | open ruling on the all-blind-embed path: NOT RUN (today) or OK |
|
||||
| 6.5 note views say something | B | running; **dormant out of the box** | needs `notes.instruction_view_sets`; `DEFAULTS` ships it empty |
|
||||
| 7.1 interference | B | running | WARN-only by ruling, over 157 findings on 57 archived tickets. Do not re-infer that the rest are noise — that was overruled once |
|
||||
| 8.1 unresolved design-summary lines | C | running | the one true FAIL of the 08-30 live run ("no probe declared"); probe added |
|
||||
| 8.2 no invisible characters | A | running | no skip probe |
|
||||
|
||||
Nothing on the list is unbuilt. Two rows are a person's job on purpose, two are muted with
|
||||
a written dated reason, the rest compute. What varies is how much of its question each row
|
||||
actually reaches — **5.1 and 5.7 claim least**.
|
||||
|
||||
**The table above is the 34-row census this section was written against and is now stale
|
||||
by count** — `checklist.master` in `knowledge/catalogue.yaml` carries **46** entries as of
|
||||
2026-08-31 (verified by reading `len(ticketpage.CHECKLIST_ITEMS)` on a fresh load; run it
|
||||
rather than trusting this number later). The rows above through 8.2 are still real; twelve
|
||||
more (`4.8`, `5.8`–`5.15`, `6.6`–`6.8`) have landed in the master since and are not
|
||||
individually described here. Extend the table from the catalogue, not from memory of it.
|
||||
|
||||
### 4.2 The other tools
|
||||
|
||||
| tool | state |
|
||||
| --- | --- |
|
||||
| **Panel Lift Check** | Finished, in daily use, no licence needed to test. Numerics open (§4.4). The per-stage input control aligned to the reference tool's `HandlingData` is a **mapping document only** (`docs/panellift-ewf-alignment.md`); nothing is built from it |
|
||||
| **Reinforcing Suggestion** | Finished. End-to-end 08-30: cold 10 s, warm ~1 s, offline byte-identical to the bridge. Position recommendation defaults **off**; one invariant test still owed |
|
||||
| **Ticket QC** | Finished, the flagship. Six in-process `vibe/ticket*.py` modules — `wc -l WillTools.extension/lib/vibe/ticket*.py`, and expect the number to have moved since you last looked; it is what §4.5 is about |
|
||||
| **Snapshot & Close** | Finished. Phases A and B measured green; phase C (the deferred detached copy) is off unless the job layer enables it, and journals its own silence |
|
||||
| **Takeoff** | Finished. `nomenclature.ticket_unit` wired 08-30, so the tool stops deciding for itself what one ticket covers |
|
||||
| **Job Recon** | Finished. Self-loading, scope-aware, 9.3 s on the real job |
|
||||
| **Design Extract** | Finished, with its caveat printed on it: every value it writes is machine-read and unverified |
|
||||
| **Self Check** | Finished |
|
||||
| **Gravity (WIP)** | Neither this nor Lateral writes to the Revit model — both are extractors: read the model, write a file elsewhere. Gravity is meant to extract information and write **Excel**. Today it does none of that: **a button with nothing behind it, and it says so.** ~200 lines of placeholder printing the open questions and assuming nothing. Blocked on the owner supplying old jobs and a control spreadsheet |
|
||||
| **Lateral (WIP)** | Meant to extract lateral-resisting members and write **RISA** — a structural analysis package — its input format. Half-built, and it does real work: it extracts marked members and writes a JSON intermediate model, two CSVs, and a `.r3d.DRAFT.txt` (`.r3d` is RISA-3D's own file extension) — the `.DRAFT` suffix deliberate, so it cannot be loaded into RISA by accident. What the RISA format needs beyond that is not established |
|
||||
|
||||
### 4.3 Dormant, and what would wake it
|
||||
|
||||
| dormant | what wakes it | who |
|
||||
| --- | --- | --- |
|
||||
| `drawings.reinforcing_rule.scopes` — empty, so 5.6 and 5.7 see one population | a ruling on three edge cases: bar-formed hardware carrying the token (one is a strand loop, with neither mark nor stock length), and reinforcement carrying none | owner, A1 |
|
||||
| `catalogue.rebar.stock_by_size` — populated, read by `knowledge.rebar_stock()`, **never called by `check_bar_stock_length`** | which parameter carries a bar's size | owner, A3 |
|
||||
| 5.3 as a computed check | what makes an elevation line dashed — line style, subcategory, or nested detail items. The view's 17 filters were read; none carries it | owner, A5 |
|
||||
| the up-end rule | woken 08-30 on evidence over 282 assemblies. Still open: the vertical-panel case, and the colour→axis mapping, which is inference not measurement | owner, A4 |
|
||||
| `tolerances.psi` (inert, muted not deleted) and `details`, the 70-entry job catalogue nothing reads | one word each: wire or delete | owner, D1 / D7 |
|
||||
| `drawings.rebar_must_appear_on` / `section_a` / `section_b` | a dated promise queued behind the current phase — not drift | queued |
|
||||
| `check_detail_callouts` as a verdict | something that reads a detail sheet's *contents*. Not built; needs the model | build |
|
||||
| vision escalation (`AI-` codes) | off by default; owns no checklist row and cannot flip one (§3.1) | config |
|
||||
|
||||
### 4.4 Known defects and unfinished business
|
||||
|
||||
- **`qc-run.json` carries findings, not the run log.** `ticketvision.qc_json` takes
|
||||
`findings` + `model_read` and nothing else — checklist rows, mutes and `run_all`'s log
|
||||
stay behind. A NOT RUN row's *reason* reaches the HTML page and `last-ticketqc.txt` but
|
||||
never the machine-readable file, so anything reading the JSON cannot tell a row that
|
||||
passed from a row nobody asked.
|
||||
- **A failing test, environment-coupled.** `test_reinforceoffline.py`'s no-index case fails
|
||||
here because `cli.resolve_index` finds the real, gitignored `knowledge/job/` and answers
|
||||
with a job list instead of the three-ways message. Green on a clean checkout, red on the
|
||||
owner's machine.
|
||||
- **Closed, 2026-08-31: every registered check now has a skip probe, and it is
|
||||
test-enforced rather than a hand count that can silently rot again.**
|
||||
`tests/test_ticketchecks_host.py::test_every_registered_check_has_a_skip_probe` walks
|
||||
`ticketchecks.CHECKS` against `_SKIP_PROBES` and fails, naming the orphan, the day a new
|
||||
check ships without one. A silent `return []` from an unprobed check still logs
|
||||
RAN-EMPTY and prints a red NOT RUN reading "no probe declared" — the mechanism is
|
||||
unchanged — but the gap this bullet used to track (eight bare entries, then seven) is
|
||||
gone; run the test rather than trusting a count copied in here.
|
||||
- **Client and plant names are in pushed history** (§3.4). **Untaken decision:** redact the
|
||||
working tree only, or rewrite history and force-push.
|
||||
- **The lifting solver's numerics.** `docs/lift-check.md`, written up, unstarted. Not a
|
||||
speed question: today's recommendations are not determined to better than several inches
|
||||
and on some pieces are not the best layout available. It gates any further
|
||||
insert-placement work.
|
||||
- **`families.wwf_prefix` has two defaults** — `takeoffgather` falls back to empty (no mesh
|
||||
detection), `ticketqc` to `'WWF'`. Reachable only when a layer sets the key null, which is
|
||||
a plant saying it has no welded wire fabric. If that is what null means, `ticketqc` is
|
||||
wrong.
|
||||
- **Smaller, recorded:** `read_ticket` costs 37.6 s on a large piece, entirely outside the
|
||||
checking wall and with no code-level cure; and `catalogue.rebar.stock_exempt_marks` is
|
||||
empty pending the 60-ft continuous-bar ruling.
|
||||
|
||||
### 4.5 The roadmap, as it actually exists
|
||||
|
||||
**`docs/architecture-migration.md` — nine steps, approved 08-30.** In-process IronPython
|
||||
keeps only the gatherers; judgement, matching and rendering move to host CPython, fed by
|
||||
the versioned JSON of §2.2.
|
||||
|
||||
*Step 1 landed 08-31*: Ticket QC writes `evidence/run-<stamp>.json` on every press and
|
||||
**nothing reads it**. As built it corrected its own plan — four live-object holdouts, not
|
||||
two, so the serialiser gates on type. **§5.1, parity on real data, is not met and could
|
||||
not be**: it needs a press, and the bridge was held.
|
||||
|
||||
*Step 2* buys a parity proof and nothing else: a host runner judging the 13 Revit-free
|
||||
checks offline against the twelve-piece audit set. Cost M, no visible change. *Step 3* is
|
||||
where it pays — the button calls that runner (reusing `liftexport.find_python`/`run`),
|
||||
making it the first press where a `ticketchecks` judgement edit is visible **without a
|
||||
Revit restart**. Cost S.
|
||||
|
||||
**§8.2, honestly:** `engine: clean` is landed on three buttons and buys nothing today. A
|
||||
clean engine is already the default here, because `extension.json` declares no `url`, so
|
||||
pyRevit's package lookup drops the extension and rocket mode never takes the clean engine
|
||||
away — verified two ways offline. It is a **pin, not a lever**, against the day somebody
|
||||
adds a `url`. **The one-minute behavioural check — press, edit a rendered string, press
|
||||
again without restarting — has still never been run**, and no bridge call answers it.
|
||||
|
||||
**`QNA/eriksson-gap-inventory.md` — the commercial-parity list.** 46 quantities: 10 at
|
||||
parity, 2 needing no build, **34 to build** (17 S · 5 S-after-dependency · 5 M · the rest
|
||||
larger). **Mcr is the cheapest first win** — S, P, A and e are held at every station and
|
||||
`modulus_of_rupture_psi` is written. The load-bearing finding: the reference tool is a **1D
|
||||
single-member analysis, not 2D FEA** — every plot is a function of x, with no mesh
|
||||
reference and no Mxy/Wood-Armer column in 18 pages. That reframes tier B entirely. The list
|
||||
needs the owner's tick before anything on it is built.
|
||||
|
||||
Beyond those two, the only genuine plans are the reinforcing-suggestion brief's open
|
||||
granularity questions and §4.3's queued coverage rule. The rest of `QNA/` is record, not
|
||||
plan.
|
||||
|
||||
### 4.6 Where the docs and the code disagree
|
||||
|
||||
Several documents were accurate when written. Where they are not, the code wins.
|
||||
|
||||
- **Every test count quoted in this tree is stale.** `CLAUDE.md` says `panellift` has 76
|
||||
tests and the extension suite is `49 passed`; `README.md` says `34 passed`. Run the
|
||||
commands in §3.7.
|
||||
- **Every line count quoted in this tree is stale, recent ones included.**
|
||||
`architecture-migration.md` §3 warns it will drift and has drifted again; on 2026-08-30
|
||||
`ticketqc.py` moved between two measurements minutes apart, because other agents were
|
||||
editing. Use `wc -l`, and read any line count as a date-stamp rather than a fact. §3.3's
|
||||
"26 registered checks" is now 27. **Verdicts hold; numbers do not.**
|
||||
- **Design Extract calls no AI**, despite `README.md` ("hands the scrape to Claude Code")
|
||||
and `CLAUDE.md`. The script says "no AI call, no subprocess", and no button routes
|
||||
through `vibe/aiextract.py` any more.
|
||||
- **`configdump.py` and `CLAUDE.md` say four knowledge layers**; `layer_values()` handles
|
||||
five, with config behind `include_config=False`.
|
||||
- **`knowledge.load()`'s docstring drops a folder** — it gives the job layer as
|
||||
`<model folder>/knowledge.yaml`, where `designspec.knowledge_path()` returns
|
||||
`<model folder>/_ticketqc/knowledge.yaml`.
|
||||
- **`docs/handoff.md` calls Panel Lift Check "the one under development"**; it is one of
|
||||
the four finished large buttons, and the same file's "Never migrate" section calls it
|
||||
*already migrated*.
|
||||
- **Batch speeds disagree across three files** — `panellift/cli.py` 41 min / 40 s,
|
||||
`docs/lift-check.md` ~40 min / 2 min, `PanelLiftCheck/script.py` 38 min / 2.5 min. The
|
||||
last is the most specific and names its path.
|
||||
- **`rails.left_rail_is_fixed` is a dead flag** (`docs/knowledge-override.md` says so
|
||||
correctly): one reader, `aiextract`, which puts it in a prompt. No check consumes it.
|
||||
Reference in New Issue
Block a user