Skip to content

feat(fabrika): implement the six glossary verbs and register the group (#5322) - #5357

Merged
usirin merged 2 commits into
mainfrom
usirin/glossary-verbs-5322-869D6E1F
Aug 10, 2026
Merged

feat(fabrika): implement the six glossary verbs and register the group (#5322)#5357
usirin merged 2 commits into
mainfrom
usirin/glossary-verbs-5322-869D6E1F

Conversation

@usirin

@usirin usirin commented Aug 10, 2026

Copy link
Copy Markdown
Member

The /glossary skill landed with a 933-line derived contract and none of the six verbs it calls
existed, so every step of the skill ran a command that exits 127. This builds all six —
init, drift, lookup, sections, add, check — registers the group, and pins the
contract's worked examples against the committed fixtures so the document and the code cannot
drift apart quietly.

Fixes #5322

What changed

New group at packages/fabrika-cli/src/glossary/: a pure core plus a thin *-verb.ts entry per
verb, a *.unit.test.ts beside each, and one codes.ts every verb allocates from.

Module What it owns
register.ts the register as data — sections, rows, normalizeKey, whole-word overlap, cell escaping
edit.ts placement, the one-row assertion, the --create-section block
findings.ts the seven check defect kinds and their fixed detail text
candidates.ts drift's Unicode-classed extraction, filter and equality-only suppression
history.ts the commit-range reads, where an empty answer is a Failure and never a fact
guards.ts the --register enum, --dir resolution, and the absent / unreadable / malformed split

Registration, each a pure insertion (see the proof below): registry.ts, GLOSSARY_SEATS +
ALIGNED_GROUPS in exit-code-alignment.ts, the TABLES map in its unit test, and a README
section. No wire format is registered — the contract calls for none, and the group emits no verdict
two skills meet through.

One shipped module changes as the contract orders: STOPWORDS in packages/fabrika-cli/src/adr/sweep.ts
becomes exported so the two term-extraction surfaces cannot drift apart. That module's tokenize is
not imported — it splits on [^a-z0-9]+, which is the ASCII-only defect drift exists to avoid.

The exit table

Shared meanings are imported from report/codes.ts under an alias; no shared numeral is restated
anywhere in the group.

Code Export Source
3 EMPTY_STDIN import (EMPTY_STDIN)
4 BAD_SECTIONS import (BAD_SECTIONS)
5 DELIBERATE_GAP held empty — leak detection is the merge gate's
6 (none) held empty by prose alone; no export, no DELIBERATE_GAP_2
7 ZERO_SCOPE import (NO_TARGET)
8 WRITE_UNKNOWN import (WRITE_UNKNOWN)
9 READBACK_MISMATCH import (READBACK_MISMATCH)
10 OFF_VOCABULARY import (CLASSIFIED)
11 PRECONDITION_UNKNOWN import (PRECONDITION_UNKNOWN)
12 TERM_COLLISION group-local
13 SECTION_ABSENT group-local
14 ROW_SHAPE_INVALID group-local
15 EDIT_BEYOND_ROW group-local

GLOSSARY_SEATS is BUILD_SEATS minus LEAKED_PATH/BARE_AT_PATH, derived from the shipped map
rather than retyped. No seat was re-seated: 5 and 6 are the base's and stay unclaimed here.

The three-way split holds: 1/127 is the call never deciding, 11 is a precondition read that
failed with nothing written, 8 is a write attempted whose outcome is UNKNOWN — and every proven
verdict at 3+ leaves stdout empty (refuse() hardcodes that).

Insertion-only proof for the shared files

git diff --numstat against the merge base, added/deleted per file:

47  0  packages/fabrika-cli/README.md
15  0  packages/fabrika-cli/src/exit-code-alignment.ts
 2  0  packages/fabrika-cli/src/exit-code-alignment.unit.test.ts
 2  0  packages/fabrika-cli/src/registry.ts
 5  1  packages/fabrika-cli/src/adr/sweep.ts

Deletions summed over the four registration files: 0. The README section is added whole; no
existing prose is reflowed or rewritten. The single deletion in the whole diff is adr/sweep.ts's
const STOPWORDS = line becoming export const STOPWORDS = — the one-line change to a shipped
module the contract explicitly requires, called out here rather than hidden inside the total.

Tests

pnpm typecheck in-package (tsgo -p tsconfig.json from packages/fabrika-cli/) is clean, and the
whole package suite passes — 269 files, 3843 tests. Coverage is three tiers:

  • Unit, in process over fakeFs/fakeShell: one *.unit.test.ts per verb plus the four pure
    modules, covering every refusal each verb can produce.
  • contract-examples.test.ts, in process over the real filesystem through NodeServices.layer:
    the contract's worked examples reproduced byte for byte against the committed fixtures under
    claude-plugins/fabrika/skills/glossary/evals/fixtures/, including check's two planted defects
    and add's derived line 18 (against a temp copy, so the committed corpus does not move).
  • glossary.cli.test.ts, three subprocess spawns for the facts only a process boundary proves:
    reachability by registration alone, the stdout/stderr split, and a refusal leaving stdout empty.

Deviations

Nine, plus one disclosed scan hit. Every one is a place the contract is silent or self-inconsistent;
none is an invention over a clause that decided.

1 — the contract's de-podepo example is UNREACHABLE, proven by execution. Normalization
step 3 says "replace every run of -, _ or whitespace with a single space", and the #4481
grounding gives the reason: a declared front-door must suppress front door. Under that rule
de-po normalizes to de po, which is not depo — so the lookup example that prints
declared terms Products (domains) depo cannot happen. The conservative branch was taken: the
stated algorithm is implemented (the one #4481 fixes), and both directions are pinned in
lookup-verb.unit.test.tsfront-doorfront door resolve as one key, and de-po answers
absent. Surfaced on #5322 rather than patched into the contract.

2 — exit 4's second stated trigger is unreachable in this parser. The contract names two: "a
header row without a separator" and "rows whose cell count the parser cannot resolve". Splitting on
unescaped | resolves a count for every line, so a short row is a row-shape finding at exit
0, not a refusal — which is also what the row-shape detail text (expected 3 cells, found <n>)
requires. 4 therefore fires only on the header-without-separator case. Pinned in
register.unit.test.ts.

3 — init seats a failed existence probe on 11, which its per-verb table does not list. The
table gives 0/8/9/10/12. A probe that could not be performed is neither "already exists" (12,
a lie), nor a failed write (8, nothing was attempted). 11 is the group's own seat for exactly
this and is not a new number. Answering "absent" instead would license a write over a register the
process never managed to look at.

4 — init's read-back arm covers a read that failed, not only one that differs. Both mean the
write landed and cannot be proven correct, which is 9's meaning; the pinned message is unchanged.

5 — drift --register both is under-determined and takes the recall-biased branch. The contract
bounds the range by "the resolved register file", singular, while --register admits both. Here
the declared set is the union of the present registers' keys and the range starts at the
older of their last-change commits, so nothing that moved since either register changed falls
outside a list the skill already treats as recall-biased. bootstrap fires only when no selected
register contributes rows.

6 — citations-unverified is emitted once per run, with - in the register/section/term
columns.
The contract fixes the kind and its detail but not which row it is reported against; it
is a fact about the corpus, not about any row. An individual record that exists and cannot be read
lands here too, with the file name in the reason.

7 — lookup --json uses null where the line grammar prints -. The line grammar's - is
pinned and is emitted verbatim; the JSON shape's register/section for an absent term are not,
and null is what a JSON consumer can branch on. matched is [], matching the declared array
shape.

8 — duplicate-key is scoped within a register. The contract says "two rows in the selected
registers" and then gives cross-register its own kind for the same key in TERMS and LANGUAGE.
Scoping duplicate-key per register is what stops one defect being reported twice under two names.

9 — two add orderings the precedence list does not reach. --replace rewrites the row where
it actually lives and reports that row's section (--section is still validated first, so a
bad one is 13). And where a register's table structure is unresolvable globally, its sections
cannot be enumerated at all, so 4 precedes 13 in that one case; wherever the register parses,
the precedence is exactly as ordered.

Disclosed scan hit — dev-tier-m.sh class 5, one line, a known false positive. The scan's bare
xit( alternative matches inside process.exit(outcome.code) in src/glossary/command.ts's emit
adapter. That line is byte-identical to the same adapter in ~19 sibling verb groups; it is not a
skipped test and there is nothing to suppress. Class 6 (removed assertions) is zero — this diff
removes no test line at all.

Two codes not reachable from a test, stated rather than left to be noticed. 15 cannot be
produced by the shipped composer, which only ever splices the lines it named — so edit.unit.test.ts
proves the assertion itself catches a re-sort and a truncation, which is the property that makes the
guard load-bearing rather than dead. 9 on add needs a filesystem that returns bytes other than
the ones written, which the in-memory fake cannot express; the arm is a straight comparison against
the composed row.

Amendment — the CodeQL thread on register.ts is fixed, not waved through

CodeQL flagged escapeCell as an incomplete sanitization, and it was right. Escaping | without
escaping the escape character left the encoding ambiguous: a cell whose own text ends in a backslash
rendered as …\ |, and that trailing backslash then read as escaping the delimiter after it — a
value able to smuggle in the column separator the escape believed it had neutralized.

The second commit makes the pair an exact inverse: escapeCell escapes \ first, splitCells
unescapes both arms, and a round-trip table covers the plain, embedded-pipe, trailing-backslash,
pre-escaped-pipe and double-backslash cases. Real corpora are unaffected — a lone backslash before a
non-delimiter still reads as itself.

Comment thread packages/fabrika-cli/src/glossary/register.ts Fixed
…oup (#5322)

The `/glossary` skill's every step opens with a `fabrika glossary …` fence and none of
those verbs existed, so the skill was inert on `main` — a model routed to it ran
commands that exit 127. This builds all six against the derived contract.

- `packages/fabrika-cli/src/glossary/` — `init`, `drift`, `lookup`, `sections`, `add`
  and `check`, each a pure verb with a thin `*-verb.ts` entry and a unit test beside it,
  over shared parsing (`register.ts`), edit mechanics (`edit.ts`), defect predicates
  (`findings.ts`), drift extraction (`candidates.ts`) and the git range (`history.ts`).
- `glossary/codes.ts` imports every shared meaning from `report/codes.ts` rather than
  restating a numeral, holds `5` as `DELIBERATE_GAP` and `6` by prose alone, and adds
  `12`-`15` on its own account.
- Registration: `registry.ts`, `ALIGNED_GROUPS` + `GLOSSARY_SEATS` in
  `exit-code-alignment.ts` and the `TABLES` map in its unit test, and a README section —
  every one a pure insertion.
- `STOPWORDS` is exported from `adr/sweep.ts` so the two term-extraction surfaces cannot
  drift apart; that module's ASCII-only `tokenize` is deliberately not imported.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@usirin
usirin force-pushed the usirin/glossary-verbs-5322-869D6E1F branch from a90458e to 3095e5e Compare August 10, 2026 18:58
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

No preview deploy

  • No preview deploy for this PR — its diff touches no deploy-relevant path, so no preview stack was minted and e2e is not applicable. (986d610)
  • web — Stage pr-5357 torn down.

…e pair

CodeQL's incomplete-sanitization finding on `escapeCell` is a real one, not a nit: escaping
`|` without escaping the escape character leaves the encoding ambiguous. A cell whose own
text ends in a backslash rendered as `…\ |`, and that trailing backslash then read as
escaping the delimiter after it — a value able to smuggle in a column separator the escape
believed it had neutralized.

`escapeCell` now escapes `\` first, `splitCells` unescapes both arms, and a round-trip table
covers the plain, embedded-pipe, trailing-backslash, pre-escaped-pipe and double-backslash
cases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@usirin

usirin commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

review-code: PASS @ 986d610 — merge-ready

Verified PR #5357 against the acceptance criteria of #5322, one at a time. The contract was read first-party from origin/main (claude-plugins/fabrika/skills/glossary/contract.md, landed by 7aaae2a2), never from the PR description.

Required namespaces for this diff (pipeline-cli class-probe classify --namespaces): 1review-code. All 33 changed paths sit under packages/, so has-code is the only class present: no .decisions/**, no claude-plugins/** skill surface, no apps/web/src/** UI path. Coverage self-check: the one required namespace carries this SHA-bound marker at head.

§CP, both axes. cp-classify classifynot-control-plane [path-clear-no-content-source]. The ADR-0164 content axis is VACUOUS, not a second confirmation: the diff carries zero .decisions/** files and guard-content-probe classify classifies exactly one ADR body, so there was nothing to feed it. Not §CP by either axis ⇒ the SHA-bound marker above, not an advisory.

Acceptance criteria

  • [PASS] all six verbs implementedinit, drift, lookup, sections, add, check each ship a *-verb.ts entry over four pure modules (register.ts, edit.ts, findings.ts, candidates.ts) plus history.ts, with a *.unit.test.ts beside each.
  • [PASS] command.ts exports glossaryCommand, every leaf via leafCommandcommand.ts:21 imports leafCommand from ../excess-operand.ts; all six leaves are declared with it (lines 56, 74, 100, 120, 132, 197). The single Command.make at line 219 is the group node, not a leaf. excess-operand.unit.test.ts green.
  • [PASS] appended to registeredGroupsregistry.ts +2/-0: the import and the array entry.
  • [PASS] codes.ts imports each shared meaning; glossary in ALIGNED_GROUPS and in the test TABLES map — all seven shared seats (3, 4, 7, 8, 9, 10, 11) are aliased imports from report/codes.ts; not one shared numeral is restated. GLOSSARY_SEATS is BUILD_SEATS minus LEAKED_PATH/BARE_AT_PATH by destructuring-rest, the shipped UI_SEATS idiom, never retyped. Registered in ALIGNED_GROUPS and in exit-code-alignment.unit.test.ts TABLES; the alignment unit test is green.
  • [PASS] exactly one DELIBERATE_GAP, on seat 5; seat 6 held by prose — one hit group-wide: codes.ts:45 export const DELIBERATE_GAP = 5. No DELIBERATE_GAP_2, no renamed second gap, and seat 6 carries no export at all, which is what makes the guard read it as unclaimed by the base.
  • [PASS] STOPWORDS exported and imported; tokenize not importedadr/sweep.ts now export const STOPWORDS, imported at candidates.ts:12. sweep.ts tokenize is imported nowhere; the tokenize inside candidates.ts is the group own Unicode-classed one, pinned against Turkish input (candidates.unit.test.ts:9).
  • [PASS] check imports isLivecheck-verb.ts:17 imports isLive from ../adr/records.ts and calls it at line 84; the liveness rule is not restated.
  • [PASS] a parenthetical is a qualifier, not an aliasnormalizeKey("Database (tag)") is database (tag) and is asserted not.toBe normalizeKey("tag"); tag resolves as collision, never declared. The glossary-drift has no duplicate-term check: a same-anchor coining collision lands silently in .glossary/TERMS.md #4206 alias split is absent.
  • [PASS] deterministic unit + CLI tests, worked examples byte-for-bytecontract-examples.test.ts reproduces the sections, lookup, check, drift, add and init examples against the committed fixtures, including the two planted check defects and the derived line 18; glossary.cli.test.ts adds the three subprocess facts a process boundary proves. In the review worktree at head: in-package pnpm typecheck clean, package suite 269 files / 3848 tests, 0 failed.

The six things graded specifically

1 — the CodeQL escaper fix is genuinely complete, not half-fixed. I re-derived the inverse property rather than reading it. escapeCell escapes the backslash FIRST and the pipe second; splitCells consumes an escaped backslash and an escaped pipe in one left-to-right pass, which makes it the exact inverse. I ran twelve adversarial cells through escape → render → split at head, including the four you named — trailing backslash, doubled backslash, an escaped pipe followed by a backslash, and an empty cell — plus a lone backslash, a pipe-only row, a cell already containing an escaped pipe, a cell containing an escaped backslash then a pipe, and a cell ending in a bare pipe. 12 of 12 round-trip exactly, 0 failures. The smuggle is closed at the source: a cell ending in one backslash now renders with two, so the delimiter after it can no longer be captured. The shipped table in register.unit.test.ts covers five of these shapes; my four extra shapes found nothing.

Fixing it here rather than filing was the right call, and I would grade a wave-through as the weaker branch: the flagged line is authored by this PR in a module this PR introduces, filing it would land a knowingly ambiguous escaper on main where every later caller inherits it, and the remedy cost one line plus five test rows. The thread was discharged by removing the defect, which is a stronger discharge than the ADR 0158 nit path.

2 — the unreachable contract example, reproduced by execution. Running the shipped normalizeKey at head: de-po normalizes to de po while depo normalizes to depo, so they are not one key; front-door and front door both normalize to front door, so they are. That is the whole contradiction. Contract step 3 folds a hyphen to a space, which is exactly what the #4481 grounding demands (a declared front-door must suppress front door), and the lookup "de-po" example needs hyphens folded away, which would re-break that same pair. The two clauses cannot both hold, and only one of them is the defect #4481 exists to fix.

Implementing the stated algorithm, pinning both directions in lookup-verb.unit.test.ts (front-door resolves declared, de-po resolves absent), and surfacing on #5322 rather than editing the contract is the right branch on three counts: the example is the bug and not the algorithm, the contract text is #5323 lane by the issue own no-gos, and implementing the example would have re-imported the exact defect the verb exists to avoid. Correctly not a blocker.

3 — the exit table holds. Seven shared meanings are aliased imports (EMPTY_STDIN, BAD_SECTIONS, NO_TARGET, WRITE_UNKNOWN, READBACK_MISMATCH, CLASSIFIED, PRECONDITION_UNKNOWN); zero bare numerals on any shared seat, which is the thing the sibling PR FAILed over today. Group-local 12 through 15 sit above the base table maximum of 11, so there is no collision. No vacated code is re-seated: GLOSSARY_SEATS drops LEAKED_PATH (5) and BARE_AT_PATH (6) and the group allocates neither — seat 6 carries no export at all, which is what makes the guard read it as still the base seat.

The three-way split is structural rather than documentary: refuse() in verb.ts hardcodes empty stdout and answer() hardcodes code 0, so a proven verdict at 3 or above carrying a machine payload is not constructible with the shipped helpers. I checked the seating at the use sites, not just in the table — 11 fires on precondition reads with nothing written (add-verb.ts lines 74, 93, 113, 119 and init-verb.ts line 48), 8 fires only at the write (add-verb.ts line 236, init-verb.ts line 63), and 9 fires only at the read-back (add-verb.ts line 249, init-verb.ts line 71).

4 — the one deletion in the whole diff. Across the entire 33-file diff against merge base 5d6ee127: 3931 lines added, exactly 1 deleted. That single deletion is adr/sweep.ts changing const STOPWORDS = to export const STOPWORDS =. The contract requires it in terms, under glossary drift → Stopwords: the list is module-private today and the contract requires the implementer to export it and import the set, the alternative being a second copy that then drifts. The four shared registration files are each strictly zero-deletion — README.md +47/-0, exit-code-alignment.ts +15/-0, exit-code-alignment.unit.test.ts +2/-0, registry.ts +2/-0.

5 — the mid-flight rebase dropped nothing from governance. The merge base is the governance commit 5d6ee127 (PR #5351), so any governance line dropped in the union resolution would appear in this diff as a deletion — and the whole diff holds exactly one deletion, which is the STOPWORDS line. Dropping a governance line is therefore unrepresentable here. That is the strongest form this check can take, which matters because the failure mode compiles and is silent.

Confirmed positively at all three conflict points as well. registry.ts carries both glossaryCommand and governanceCommand, in the imports and in registeredGroups. The alignment unit test TABLES map carries both glossary and governance. exit-code-alignment.ts keeps governance: GOVERNANCE_SEATS beside the new glossary: GLOSSARY_SEATS. The README carries the glossary group section and the governance group section as separate headings. And the whole package suite, governance tests included, is green at head.

6 — the ten silences, graded one at a time. Every one is a place the contract is silent or self-inconsistent, every one took the conservative branch, and none of them should have blocked. Four are surfaced on #5322 and six more in the PR ## Deviations section.

# Silence Grade
1 lookup "de-po" example unreachable right — see above; the example is the bug, the fix is #5323 lane
2 exit 4 second trigger unreachable in this parser right — splitting on unescaped pipes resolves a count for every line, and check row-shape detail (expected 3 cells, found <n>) needs that same count at exit 0. A refusal here would contradict a pinned detail string. Pinned in register.unit.test.ts
3 init seats a failed existence probe on 11, unlisted in its table right, and fail-closed — the alternatives are both lies: 12 claims it exists, 8 claims a write was attempted. Answering absent would license a write over a register the process never managed to read
4 init read-back arm also covers a read that failed right — both facts are the write landed and cannot be proven correct, which is what 9 means; the pinned message is unchanged
5 drift --register both has no stated range right branch, but the choice itself is UNRULED — see below
6 citations-unverified emitted once per run with - columns right — the contract fixes the kind and its detail but not the row it is reported against, and it is a fact about the corpus rather than about any row
7 lookup --json uses null where the line grammar prints - right — the line grammar - is pinned and is emitted verbatim; the JSON shape for an absent term is not pinned, and null is what a JSON consumer branches on
8 duplicate-key scoped within a register right — the contract gives cross-register its own kind for the TERMS/LANGUAGE case, so per-register scoping is what stops one defect being reported twice under two names
9 two add orderings the precedence list does not reach right and narrow--section is still validated first so a bad one is still 13, and both orderings are fail-safe
10 init --register language body sentence is unpinned right — the contract pins the TERMS template as its single home and says only that the LANGUAGE body sentence names the architecture vocabulary. The minimal edit was taken and explicitly disclaimed as this implementation choice rather than the contract. Cosmetic bootstrap text no consumer greps

Recorded UNKNOWN — not invented, not graded away

  • The drift --register both range is genuinely unruled. The contract bounds the range by the newest commit that touched the resolved register file, singular, while --register admits both. The union-of-declared-keys half is not a choice at all — a key declared in either selected register is declared, so suppressing it is simply correct. The older-of-the-two-last-change-commits half is a real decision the contract does not make, and the opposite choice (the newer commit, a narrower range) is defensible too. It could not usefully have blocked: drift is off the fail-closed per-PR gate by construction (ADR 0128, and the contract own note that this verb reports and never reds a merge), so a wrong choice here reds nothing. Surfacing plus implementing was right; the contract owes a clause, and that clause is feat(fabrika): author the glossary skill and derive its CLI contract (#4711) #5323 lane. Recorded as UNRULED, not resolved by this gate.
  • The init --register language body bytes are unpinned. They are this implementation choice, disclosed as such on Six fabrika glossary verbs are specified and none exists #5322. A later reader must not treat them as the contract.
  • The check no unaccounted unresolved review thread reaches merge-ready check-run is red at head, and it is stale. It ran at 19:03:13Z — about 90 seconds after the fix commit 986d6100 (19:01:45Z), while the CodeQL thread was still unresolved and before any verdict existed. Re-running the identical verb (pipeline-cli unresolved-threads-guard check --pr 5357) against live state now: scanned 1 review thread(s), 0 unresolved, all accounted-for — clean. I cannot re-run a CI job; ship-it must see this check green before it enqueues. I do not treat a stale red as a code defect, and I do not wave it either.

Sub-gates

  • ## Deviations: present and NOT None. — re-ran the canonical §DEV Tier-M scan at head: section present, 1 suppression/skip line, 0 removed-assertion lines. The single hit is +\t\tprocess.exit(outcome.code); in src/glossary/command.ts, matched by the scan bare xit( alternative inside process.exit( — exactly the false positive the PR body discloses by name. I verified it is not a novel line: that identical emit adapter appears in 21 files across the package. Class 6 is genuinely zero — this diff removes no test line at all.
  • unresolved-threads — one thread, packages/fabrika-cli/src/glossary/register.ts:228 (github-advanced-security, CodeQL incomplete string escaping). It is resolved at head, by fixing the defect in commit 986d6100, not by a rationale-and-move-on. Accounted for here; 0 unresolved remain.
  • comment-discipline — scanned 3483 added lines on comment-bearing files. The added comments are KEEP-category under the deslop-comments rubric: the escapeCell note is a local invariant at its enforcement site (the escape order is the correctness of the pair), the splitCells note states the inverse property, the sweep.ts note gives the export forcing constraint plus the do-not-import-tokenize trap, the GLOSSARY_SEATS note says why 5 and 6 are unclaimed, and the test docblocks carry the The scheduled glossary-drift sweep re-files a class a standing founder ruling kills — retire, retune, or gate its filing #4481/glossary-drift has no duplicate-term check: a same-anchor coining collision lands silently in .glossary/TERMS.md #4206 grounding and the measured contract divergence. No narration, no name-restating, no ADR re-derivation. PASS.
  • glossary-freshness — not applicable: no new feature folder, public package or public export in this PR, with the detector expressible here (50 candidate surfaces on base). A real skip, not a blind scan.
  • flag-gating (Step 3b) — no-op: Six fabrika glossary verbs are specified and none exists #5322 carries no **Containment:** marker (containment-marker.shnone).
  • typecheck — in-package only, per Root tsconfig.json has no noEmit, so tsgo -p tsconfig.json writes output into the tree #5312; tsgo -p tsconfig.json was not run from the repo root. Clean.

Run-evidence bundle: PRESENT for head 986d6100 — producer run 31422148711, artifact 9075900266, manifest.commit == head, schemaVersion 1; checks 2/2 pass; tests 2424/2424 passed, 0 failed, 0 skipped.

Read the PR head (§HEAD): every file under review was sourced from 986d6100c32515bcb2b35c3d8aa194037d422d64 via a per-run ref and an isolated throwaway worktree, never from the launched checkout working copy. No working tree was ever switched. The head was re-confirmed unmoved immediately before this post.

One note, no action asked: the PR body Tests section still says 3843 tests, while the head actually runs 3848 — the five round-trip rows the fix commit added. The Amendment section and the #5322 progress comment both carry the current number. Stale prose, not a code defect.

All criteria pass. This PR is merge-ready. review-code does not mergeship-it is the authorized merge step; merging will auto-close #5322 via Fixes #5322.

Verdict-written: 2026-08-10T19:21:01Z

@usirin
usirin added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit 4a22a1b Aug 10, 2026
46 of 47 checks passed
@usirin
usirin deleted the usirin/glossary-verbs-5322-869D6E1F branch August 10, 2026 19:40
@github-actions github-actions Bot mentioned this pull request Aug 10, 2026
usirin added a commit that referenced this pull request Aug 10, 2026
… pair (#5364)

CodeQL's incomplete-escaping finding on `cellText` is a real defect, not a nit. Escaping
`|` without escaping the escape character leaves the encoding ambiguous: a cell carrying a
backslash-pipe sequence emitted an unescaped pipe, so a three-cell row rendered as four.

This ports the implementation proven on the sibling module in PR #5357 (`escapeCell` /
`splitCells` in `src/glossary/register.ts`) rather than deriving a second one. `cellText`
escapes `\\` first and `|` second; `rowCells` consumes both arms in a single left-to-right
pass, which is what makes it the exact inverse. The old splitter's `(?<!\\)` lookbehind
mirrored the escaper's bug, so the pair round-tripped itself while a renderer disagreed —
which is why the round-trip table is asserted against composed rows and not against the
splitter alone.

Twelve adversarial cells cover trailing backslash, lone backslash, doubled backslash,
escaped-pipe-then-backslash, escaped-backslash-then-pipe, pre-escaped \\|, pipe-only,
trailing bare pipe and the empty cell.

The share-vs-duplicate question these two escapers raise stays open on #5364.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Six fabrika glossary verbs are specified and none exists

2 participants