# Workbench Report # # What spwashi.com has learned about Spw by using it. Addressed to spw-workbench: # every entry is a gap, a defect, or a command this consumer had to build because # the CLI did not carry it. Measured on a mounted consumer, not theorised. # # Read this when asking what a real consumer has to say about the language and # its tooling. Each finding states its probe so it can be reproduced or refuted. #>spw_workbench_report #:report #!workbench #!consumer #!feedback #:operation #!audit #:fixity #!tending #:layer #!pragmatics @mount: ~"./mount.spw" @canon: ~"./_workbench/.spw/index.spw" @cli_convention: ~"./_workbench/.spw/conventions/cli.spw" @precipitates: ~"./_workbench/.spw/runtime/precipitates.spw" @expression_formations: ~"./language/expressions/index.spw" @semantic_expression: ~"./conventions/semantic-expression-consequence.spw" @integrity: ~"../scripts/spw-integrity.mjs" @dimensional_index: ~"../scripts/spw-dimensional-index.mjs" @wonder: ~"../scripts/wonder.mjs" @precipitate: ~"../scripts/spw-precipitate.mjs" @recommendations: ~"./reviews/upstream-workbench-recommendations.spw" @history_strands: ~"./caches/history-conflict-strands-2026-09.spw" operation = "audit" fixity = "tending" expression = consumer[report]{defect.gap.built} ^"walk_cost"{ ~#severity(highest): "one fix dominates every other item here" claim: "`--from .spw` is unusable on a mounted consumer because the walk descends into the mounted workbench and its node_modules." measured: .{ consumer_roots_enumerated_ms = 11 spw_root_ms = ">120000 (timed out)" files_under_spw = 6553 node_modules_dirs = 6 spw_files_consumer = 268 spw_files_workbench = 551 }[reg=facet] ~#reading: `naming the parent costs four orders of magnitude more than naming its children` fix: "exclude node_modules, .git and a mounted _workbench by default; --include-infrastructure to opt back in" ~#consequence: "every consumer script here shells find with -not -path '*_workbench*' rather than using --from" history = `Named as consumer[walk]{roots} ^ workbench[mount]{node_modules} in @history_strands#workbench_explore. Explore the workbench after a pruned walk, not before.` 2026_09_astra = `At pin f2e5b61, query already excludes _workbench; a pathRefs count still exceeded 15s. Shared walker: 681 files, 374 mounted, 307 without the mount. Isolation is still useful; it is not the proven timeout cause. Instrument discovery vs per-file parse next.` ~#resolved(2026-09-09, workbench f92382f, pin 14b4b47): "Default walks skip _workbench and build; --include-infrastructure / --all opts back in. doctor scan-exclusions: .git, node_modules, dist, release, _workbench, .agents, .spw/gen. Remaining cost is parse, not mount descent: conventions pathRefs --stats files_walked=118 parse_ms=13566.8 total_ms=13778.9." nested_agent_worktrees = `A git worktree nested under the consumer (.claude/worktrees/…) is not canon. Doctor already excludes .agents; it does not name .claude/worktrees. Ignore the directory in the consumer; prune leftovers with git worktree remove when the branch has no unique commits.` probe: `spw query --from .spw/conventions --selector pathRefs --count --stats` } ^"parse_cost"{ ~#severity(highest): "with the walk fixed, parse is the whole cost; the next saving is not reading the same surface twice" claim: "Every query, resolve, and lint call parses every surface from scratch, and a few syntax shapes cost far more than their size." measured: .{ conventions_parse_share = `22.6 s of 23.7 s wall (95%); walk, read, evaluate, and format together about 1 s` corpus = `378 surfaces, 3.29 MB, pin 3eaab63; 209 s of 219 s is parse` clean_rate = `341 surfaces at 18.7 KB/s` error_rate = `37 surfaces with parse errors at 7.8 KB/s: 10% of files, 15% of bytes, 31% of parse time` spread = `0.7 to 137 KB/s across surfaces over 8 KB; the slowest 20 hold 46% of parse time` record_rows = `32 rows of .{ … }[reg=facet] inside #[ … ][reg=set]: about 20 ms a row; without the postfix 13, without the inner list 10.5; quote, brace, and binding spelling change nothing` stray_backtick = `one unbalanced backtick in the first of those 32 rows: 579 → 4034 ms (7×)` boot = `about 1.0 s per CLI call through the tsx loader, before any surface is read` cache_hits = 0 }[reg=facet] ~#load: "measured 2026-09-22 with a machine load average between 10 and 60 from concurrent sessions; the ratios are the finding" fix: .{ architecture = `let query, resolve, and lint host the corpus scan memo (census and graph already do), or a per-file plane keyed by file hash, dialect, and selector; one walk for several products; emitted JS beside the tsx route` syntax = `bound error recovery for an unterminated backtick string; before any parser change, a lexer-only check for unbalanced backticks lets authors remove the cost` hypothesis = `memoize alternatives at bracket starts in the record grammar; the retry path has not been traced, so the 32-row block is a regression fixture, not a diagnosis` }[reg=facet] ~#consequence: "spw:integrity takes about 80 s here, and the gate cannot afford it; it runs nightly in CI instead" probe: `spw query --from .spw --selector pathRefs --count --stats --profile` } ^"parse_entry_points_disagree"{ claim: "parseExpression truncates where parse structures, and reports success while doing it." ~#resolved(2026-09-03, workbench 75d8f9d26253, commit f3061c5): "verified live: surfaces[route]{path.role.archetype} now consumes the full 45 characters under parseExpression and returns one Expression node — the same fix that binds same-line [frame]/{body}/ onto one noun for parse() too. The fix asked for below (`fix:`) landed. Kept this facet rather than deleting it: the cost line is the reason the gap mattered, and the site's own tooling (spw-runtime-parser.js, spw-block-association.js) still names entry: 'parse' as the default on principle, not because parseExpression is broken anymore." example = `surfaces[route]{path.role.archetype}` parse_result: "Capsule → Operation → ModifierChain → Frame → Parameter → Body" parse_expression_result_before_fix: "consumes 8 of 45 characters, returns success: true" parse_expression_result_after_fix: "consumes 45 of 45 characters, returns one Expression with frame/body as its own fields" measured: .{ authored_expressions = 461 structuring_under_parse = 461 surviving_parse_expression = 3 }[reg=facet] ~#reading: `only sigil-led forms begin an operation in the standalone entry point` ~#cost: "an early consumer check used parseExpression and concluded the site's copy was malformed. The copy was correct and the API was not." fix: "make parseExpression consume the full expression, or error rather than under-consume — DONE" } ^"project_join_degrades_to_prose"{ ~#note: "Found re-verifying the corpus against the 2026-09-03 rebuild above, not present against 993c0994d016." claim: "~> (project-join) inside a {body}, with nothing after it to close a , now degrades the whole parse to Prose rather than structuring it — success:true, 0 errors, but a warnings entry names it explicitly." example = `cauldron[garden]{sow ~> tend ~> harvest}` warning_message = `Structured parse stopped at CAPSULE_CLOSE ">"; surface degraded to prose.` ~#reading: `the same postfix-binding work that fixed parseExpression above appears to read the lone > in ~> as an unmatched capsule-close now, where 993c0994d016 read it as two ordinary characters` ~#cost: "none measured on this site — every consumer here (readJoinChain, kernelJoinFromTokens) reads ~> at the string/token level and never trusted the assembled AST's node types beyond parse().success/errors.length, so all 14 authored project-join expressions still resolve correctly through the manifest and spw:integrity. A consumer that walks the AST instead of the tokens would see Prose/ProseChunk where it expected Sequence/Operation." fix: "disambiguate ~> from a lone capsule-close, or lex ~> as one PROJECT token rather than OPERATOR '~' followed by CAPSULE_CLOSE '>'" ~#superseded(2026-09-03, same day, workbench f2e5b61): "still open in the grammar at that pin — this was not what f2e5b61 fixed — but moot for this site's own 14 authorings: all 11 that used ~> for a plain ordered sequence were converted to ; once ; became real. ~> stays reserved for movement between distinct places." ~#resolved(2026-09-09, workbench f681d04, pin 14b4b47): "Lex ~> as one project-join connector. Verified: spw fingerprint --expr 'cauldron[garden]{sow ~> tend ~> harvest}' reports complete=true prose=false root=Expression Sequence=1." } ^"sequence_separators_ranked"{ ~#note: "Landed same-day as the parseExpression/noun-form fixes above, in a separate commit — Claude-Session 01AxX96buLHeJocQJspdtELg, not this site's session." claim: "; (sequential) and || (parallel) now rank as real sequence separators — expressionImpl stops its chain loop at them, Sequence.separators records which mark was written, and frame content accepts them beside comma. Verified live: cauldron[garden]{sow;tend;harvest} parses as Sequence with success:true, 0 errors, 0 warnings — clean, where the ~> form for the same content still degrades to Prose (see the facet above)." ~#reading: `this is the exact gap this report itself named in the parse_entry_points_disagree facet's era: ";" is site ordinal until the default lexer emits it as a connector" (interaction-microstates.spw, compound-expressions.spw) — written the same day it landed` ~#landed(2026-09-03, this site): "converted the 11 authored expressions that used ~> for a plain ordered sequence to ; — cauldron[garden]{sow;tend;harvest} and 10 more. readShape's existing fallback to body.parts (regex-captured, not token-scanned) already handled the ordinal case correctly with no further fix needed — the parts-scoping bug found for project/crawl did not apply here." probe: `node -e "import('./public/js/semantic/spw-workbench-parser.js').then(m=>{const r=m.parse('cauldron[garden]{sow;tend;harvest}\\n');console.log(r.ast.expression.type,r.success,r.warnings.length)})"` } ^"lattice_reads_one_form"{ claim: "spw lattice counts ~#name(body) unit cells only, and reports an empty field for a corpus full of appositions." measured: .{ cells_reported_before = 6 surfaces_scanned = 237 plain_apposition_present = "thousands" }[reg=facet] ~#reading: `an empty field presented as a finding is worse than an error` fix: "read ~#name: as a cell, or report the two forms separately so the gap is visible" ~#resolved(2026-09-09, workbench 5f18124, pin 14b4b47): "CLI lattice counts ~#name: as a distinct colon species. Advertised on this pin; not re-censused here." } ^"no_annotation_lint"{ claim: "A malformed axis declaration is silently invisible rather than reported." example = `#:operation contract` correct = `#:operation #!contract` found = 4 ~#consequence: "four surfaces were absent from every axis query until the sigil was restored" fix: "spw lint — well-formed annotations, known axis values, anchor uniqueness" ~#resolved(2026-09-09, pin 14b4b47): "spw lint is a command. Flags `#:operation contract` (missing #!) and duplicate #> names inside one surface." } ^"missing_intermediate_form"{ ~#severity(most reusable): "the one addition most likely to stop consumers re-deriving badly" claim: "A citation arrives as an opaque string. Every consumer that wants to follow one re-implements splitting, classification and resolution." wanted: "a resolved citation — target split into path and fragment, classified as file | route | directory | external | malformed, with existence known" ~#note(cite and follow): "address content hashes, not references — a different question" ~#cost: "the first attempt here used a regex and reported 59 false positives on ~\": \" inside prose values. The parser already knew; the regex did not." built = @integrity fix: "spw resolve — or a --resolve flag on query --selector pathRefs" ~#resolved(2026-09-09, pin 14b4b47): "spw resolve classifies pathRefs as file|route|directory|external without treating ~\": \" prose as a citation. Site @integrity remains the existence walk this corpus already had." } ^"built_because_absent"{ ~#note: "Each is a consumer script that would be better as a command." resolve: .{ built = @integrity, does = `resolve every citation to file and anchor, classified`, ~#landed(2026-09-09, pin 14b4b47): `spw resolve now classifies; @integrity still checks existence` }[reg=facet] axes: .{ built = @dimensional_index, does = `group the corpus by the operation, fixity and layer surfaces declare about themselves` }[reg=facet] wonders: .{ built = @wonder, does = `harvest ?[…]{ !probe{} } and resolve each $%[…] substrate against its producer`, still = `no spw wonder command at pin 14b4b47` }[reg=facet] precipitate: .{ built = @precipitate, does = `lift probe output into a cache with deltas against the prior run` }[reg=facet] ~#reading(the pattern): `census answers what the corpus is; none of these answered what it claims about itself` } ^"round_trip"{ claim: "The --spw products are readable and not re-ingestible. Nothing here ever piped one back in." ~#reading: `--json is the only composable path, which undercuts the dual-read premise` fix: "make collate products parse as input, so census | query composes" ~#note(this matters upstream): "@precipitates states the invariant that every stage renders its precipitate as valid Spw. Rendering is not the same as being re-readable." } ^"instrumentation"{ claim: "No timing, no walk counts, no cache-hit reporting. The memo line says fresh and never says what it saved." ~#consequence: "the walk_cost finding took a two-minute timeout to notice; --stats would have shown it in one run" fix: "--stats on collate commands: files walked, files parsed, bytes, cache hits, elapsed" ~#resolved(2026-09-09, pin 14b4b47): "spw query --stats prints files_walked, files_parsed, bytes_read, cache_hits, parse_ms, total_ms. cache_hits stayed 0 on the conventions pathRefs run." } ^"what_landed_well"{ ~#note: "Stated so the report is a reading rather than a complaint." form_stdin: .{ command = `spw form --stdin` ~#reading = `the only command that said something countable-by-hand was not already known` example = `shallow nesting — good for teaching contours one bound at a time` ~#why = "operator distribution, medial capsules and a learning ladder from a single expression" }[reg=facet] parse_in_file_context: "structures the authored noun form correctly and completely" refactor: "renaming marks by structure rather than by grep is what makes a vocabulary decision cheap to defer" } ^"insights_unused"{ ~#note: "Read once, never acted on. Recorded because an unused insight costs output width." topo_roles: `hub | source | leaf | orphan — degree already answered what to read next` cyclic_flag: `reports that a cycle exists without saying whether it matters` ~#missing: `node-shape fingerprint — Operation, Frame, Body, Capsule counts for one expression, run perhaps fifteen times here by hand` fix: "spw fingerprint — how any grammar claim gets verified" ~#resolved(2026-09-09, pin 14b4b47): "spw fingerprint / spw fp. Used to verify the ~> project-join landing." } ^"wonder_harvest_absent"{ pin = `3eaab6377672` prior_pin = `14b4b47763aa` charge_sign = `3eaab63 attaches the modifier chain to the expression. 2026-09-22: parseExpression("boon.honk home[hook]{orient}") is complete, mods=boon.honk, no prose-degradation. honk[signal]{resonance} and boon[honk]{invite} structure the same way. Scope and capsule on cycle[b]{receipt}(progress) still bind.` claim: "The consumer-cli-instruments pin landed fingerprint, resolve, lint, walk isolation, and query --stats. 3eaab63 adds charge-signs and does not add a wonder harvest. scripts/wonder.mjs still walks ?[…]{ !probe{} $%[…] }. Answers authored as grok = inside the wonder body are invisible to that harvest, to LSP parseWonderBlock (12-line cap, lens from // lens: not ~#lens()), and to probe-measure (question / !probe / $% only)." astra_pass = `npm run wonder -- --surface astra-design still prints four open questions after grok=/claude=/agy=/astra= have landed in the cache.` refuse = `Do not add a seventh wonder type or ASTRA.md. Reuse ~#model plus a ~#grok: cell, or a neighboring !land{} / !boon{} procedure after the probe — flow-protocol already names procedure-after-probe.` first_patch = `Either consume: author landings as ~#grok: so field() sees them; or upstream: brace-walk parseWonderBlock, count the ~#model binding, and add spw wonder --unanswered.` probe = `npm run spw -- wonder --help — still an unknown command at 3eaab63` }[reg=facet] #>wonder_workbench_report_landings ?["Can harvest tell a finished prime from an open question when the answer is grok = inside the wonder body?"]{ #:depth #!computational ~#lens(inquiry) ~#with: @wonder ~#hypothesis: "question, !probe, and $% are visible. grok = is an assignment no harvest, lattice, or probe-measure counts. ~#grok: would already be a cell." ~#tension: "a completed Astra prime still harvests as four open questions. Adding a seventh wonder type would be the counterfeit." !probe{ "npm run wonder -- --surface astra-design after grok=/astra= land. if the output cannot say answered, the landing is invisible. npm run spw -- wonder --help should fail as an unknown command. It still does at pin 3eaab63." } $%[wonder.answered, landing.form, cli.wonder] } #>wonder_workbench_report_1 ?["What would let a model onboard from the corpus rather than from a README?"]{ #:depth #!educational ~#lens(arc) ~#tension: "degree, freshness and operation are all computable and none is composed. A reader is told what exists and never what to read first." ~#hypothesis: "hubs by degree, sorted by staleness, filtered to contract and prime, is an onboarding arc — and every input already exists" !probe{ "compose census degree with git-derived age and the declared operation. if the top rows are what a person would actually hand a newcomer, the arc is computable and unwritten." } $%[surface.degree, surface.age, surface.operation, reader.firstRead] } ^"browser_parser_testbed"{ claim: "The portable parser kernel builds directly into public/js/semantic/spw-workbench-parser.js and runs lossless AST tests in the browser at /tools/spw-parser/." verified_specimens: #[ "v04_claims (hypothesis, citations, falsification probes)", "profiles (file shape declarations with required structural blocks)", "dot_crawl (complete brace traversal {a}.{b}.{c} ~> [loaf])", "stem_projection (multilateral substrate precipitation >stem[name]{...})", "boonhonk (pantry combinatorics and transformational genres)" ][reg=set] testbed_route: ~"/tools/spw-parser/#v04-specimen-suite" builder: ~"../scripts/build-spw-literal-parser.mjs" } ^"validation"{ probe: `npm run spw:integrity && npm run spw:dimensions` falsification: "a finding here no longer reproduces from its stated probe against the pinned submodule" ~#note(pin): "measured against the submodule as mounted; re-run after a bump before carrying anything upstream" }