feat(explain): ground the operator axis on the device (#255) — step 1 - #287
Conversation
#255 step 1: sweep the RouterOS operator surface rather than transcribe the manual's list, the way #252 had to redo the escape set. Grounding only — no span, no new class, no defect rule. `explainCommand` output is byte-identical to main on the six baseline inputs. Three tools, three links, each with its own gate: - `explain:operator-census` mines IL heads from the 2,739 committed corpus `:parse` captures (no CHR). It is a CANDIDATE GENERATOR only: RouterOS IL is a debug rendering with no string quoting, so string content lands in head position, and the census reports shape rather than filtering. - `explain:probe:operators` sweeps the manual's list, plausible non-operators and the census heads on CHR 7.23.3 stable and 7.24rc4 testing, with `:parse` IL as the naming oracle and `highlight` for structure-vs-word only. - `explain:operator-slice` cuts the reviewed fixture; `src/explain/operators.ts` is held to it by `explain-operators.test.ts`, and the README table is generated from the table and gated in `lint:ci`. What the device says that the manual does not: - `not`, `xor`, `mod`, `is`, `div`, `band`, `bor`, `shl`, `shr`, `eq`, `ne` are NOT operators. Inside parens a bare word is a variable reference, so `(1 not 2)` parses — as `( 1 $not 2)`, an unnamed juxtaposition node. "It parsed" is not evidence of an operator, which the `(1 zzz 2)` control proved by failing the probe's first verdict rule. - `..` is `.` plus a variable named `.`; `//` likewise; `<>` is `(< 1 (> 2))`. - `$`, `[`, `]` never head a node — they are on the substitution axis. - `any` is an undocumented prefix operator. - `&&`/`||` are spellings of the `and`/`or` nodes. Precedence is measured, not transcribed: over all 576 ordered pairs the outer counts land exactly on `2*(tighter) + (same level)` with 0 dropped, giving 14 levels. Associativity has three answers, not two — nine operators are variadic because the device flattens them. `highlight` cannot give an operator boundary: `syntax-meta` is the residual structure class (it also covers quotes, braces, brackets, whitespace) and adjacent runs are merged. #255's framing of it as the operator class is corrected in the README. The `(>…)`/`<%%` axis is covered: arity is the only thing separating the deferred-expression `(> x)` from the comparison `(2 > 1)`, `[:typeof]` of a deferred command body is `op` while a deferred array is `array`, and `<%%` binds positionals from `$0` where a `do={…}` function binds from `$1`. Identical on both versions except one runtime row: array comparison with `>` errors on 7.23.3 and evaluates on 7.24rc4. Emission is out of scope and stays #264 B2 (operator fill) and B4 (the `centrs → highlight` projection); both read this table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe PR adds a device-grounded RouterOS operator registry. It introduces CHR probes, corpus census tooling, committed fixture data, unit tests, generated operator documentation, and CI drift checks. ChangesRouterOS operator surface
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant OperatorSweep
participant RouterOS_CHR
participant OperatorSlice
participant OperatorFixture
OperatorSweep->>RouterOS_CHR: Send parse, highlight, and runtime probes
RouterOS_CHR-->>OperatorSweep: Return device captures
OperatorSweep->>OperatorSlice: Provide versioned capture JSON
OperatorSlice->>OperatorFixture: Generate operator metadata and fixture data
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Muse Review
Review of #255 grounding — no blockers, 5 follow-up nits/gaps. Grounding method (IL oracle + zzz control + strict weak order) is solid. See inline comments.
Muse ReviewFollow-up probes:
Overall: |
…/! tighter than all binaries) any is prefix arity 1, :typeof always bool, false only for nil/nothing (the :if (any $x) idiom). Infix 'any' is juxtaposition, not an operator. Proved on 7.23.3 + 7.24rc4 + 7.21.5 long-term and corpus 7.20.8 (any|7.20.8:2) that !/any (and unary ~/-/>) bind tighter than every binary including ->/ <%%. - any: 9 new runtime rows (undefined-var, defined-var, nothing, true, false, and/or with nothing, juxt, concat) grounded on both CHR versions; fixture runtime 8→17, versionDifferences still 1 (array-compare). - probe: RUNTIME extended with those 9. - operators.ts: doc for any nil-check and unary level 15 note. - README: any bullet now mentions nil-check + juxt/concat + 7.20.8. - test: two new cases for any nil-check and since-7.20.8, plus headVersions type. Results unchanged: 26 operators, 14 binary levels, 0 dropped.
|
Follow-up for
No operator count/precedence change — grounding-only clarifies |
There was a problem hiding this comment.
Pull request overview
Adds a device-grounded operator inventory for RouterOS scripting (issue #255 step 1), including tooling to (a) generate candidate operator spellings from an offline IL-head corpus census, (b) sweep those candidates on CHR via :parse + highlight, and (c) slice the reviewed results into a stable in-repo table used by docs and future token emission work.
Changes:
- Introduces
src/explain/operators.tsas the durable, device-derived operator table (operators + lowered spellings + grounded non-operators). - Adds probe + slice + census scripts and wires new corpus/doc gates into CI and
lint:ci. - Adds fixtures and unit tests to lock the table to the device sweep output and keep generated README content in sync.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/explain-operators.test.ts | Unit assertions that the operator table matches the committed sweep fixture and preserves key grounded facts (e.g., not not-operator, > arity split). |
| test/unit/explain-operator-probe.test.ts | Unit tests for the pure helper functions used by the probe/census/sweep logic (verdicting, marker splitting, IL reading, quoting, run coalescing). |
| test/fixtures/explain/operators.json | Committed slice of corpus census + CHR sweep results that backs src/explain/operators.ts and tests. |
| src/explain/operators.ts | New durable operator surface table (operators, lowered spellings, grounded complement) with accessors. |
| scripts/probes/explain-operator-sweep.ts | CHR probe that sweeps candidates via :parse IL + highlight, computes precedence/associativity, and emits captures. |
| scripts/probes/AGENTS.md | Documents the new operator sweep probe and its durable outputs. |
| scripts/explain-operator-slice.ts | Reduces raw sweep captures into the committed fixture and generates/gates the README operator tables. |
| scripts/explain-operator-census.ts | Offline corpus census to enumerate IL heads (candidate generator) and gate the committed corpus block in the operators fixture. |
| package.json | Adds operator census/slice/probe/readme scripts and adds the operator README check to lint:ci. |
| GLOSSARY.txt | Adds operator-axis vocabulary terms used in docs/tests. |
| commands/explain/README.md | Adds operator-surface documentation and includes the generated operator tables gated by explain:operator-readme:check. |
| .github/workflows/ci.yaml | Extends the corpus job to run and gate the operator census (and publishes it in the job summary). |
Suppressed comments (1)
commands/explain/README.md:1148
- In the generated “Spellings the device reads as something else” table,
||is rendered as`\|\|`, which displays/copies with backslashes. This is generated output; adjust the generator so pipe-containing spellings render as||while keeping the table cell valid.
| -------- | -------- | ---- |
| `&&` | `(<%% (and 1 2) )` | alias |
| `\|\|` | `(<%% (or 1 2) )` | alias |
| `<>` | `(<%% (< 1 (> 2)) )` | re-lexed |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/explain-operator-census.ts`:
- Around line 305-314: Escape pipe characters in the head value before
interpolating it into the Markdown table row returned by the heads map. Update
the head rendering in the map callback while preserving the existing shape,
scripts, occurrences, arity, and per-version columns.
- Around line 423-436: Update the --check flow in the explain operator census
command to return a nonzero status whenever result.resolution.warning is set,
even if diffAgainstFixture returns no drift. Keep the existing drift reporting
and success path unchanged for cases without a resolution warning.
In `@scripts/explain-operator-slice.ts`:
- Around line 481-495: Validate each parsed capture before passing it to
buildSweep, requiring the expected precedence shape and reporting the offending
path with an actionable message. In the --out fixture read near outPath, handle
a missing or unreadable file with a clear error stating that the fixture must
already exist and contain the corpus block, while preserving the existing write
behavior.
- Around line 397-407: The explanatory text in the output-building logic should
derive the prefix-only operator count from the existing prefixOnly collection
instead of hardcoding “two.” Update the sentence near the prefixOnly.map block
to interpolate prefixOnly.length while preserving the surrounding wording and
generated README formatting.
- Around line 352-375: Move the README render-chain documentation block so it
immediately precedes the renderReadmeBlock function, leaving the cell function
and its markdown-escaping documentation together. Ensure each doc block directly
documents its intended symbol and preserve both documentation contents
unchanged.
In `@scripts/probes/explain-operator-sweep.ts`:
- Around line 95-99: Update ParseResult.deviceSource and its assignment in
describeParse to avoid claiming it is the exact device command when batch mode
adds marker and concatenation statements; either record the actual command
emitted by parseMany or narrow the field documentation to describe only the
parse wrapper.
In `@src/explain/operators.ts`:
- Around line 272-322: Update the NOT_OPERATORS doc comment to state that there
are eighteen word-shaped entries, matching all word-shaped rows in the array
from not through outside; leave the operator definitions and surrounding
explanation unchanged.
In `@test/unit/explain-operator-probe.test.ts`:
- Around line 45-96: Add a `verdictOf` test covering an accepted carrier whose
`ilHead` matches the spelling and whose `ilArity` is null, asserting it is
classified as an operator rather than `not-an-operator` and does not incorrectly
populate arities.
- Around line 1-25: Add direct unit tests in explain-operator-probe.test.ts for
the slice helpers outerCounts, precedenceLevels, associativityOf, and whyNot.
Cover equal-count grouping, non-conforming counts, and variadic,
left-associative, and right-associative cases, using the existing test
conventions and importing the helpers from explain-operator-slice.ts.
In `@test/unit/explain-operators.test.ts`:
- Around line 306-313: Remove the `runtime("any-nothing").output` assertion and
its 7.21.5 claim from the test; retain only the fixture-backed corpus assertions
in the test named ``any` has been an operator since at least 7.20.8`.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ae7b2eaf-f540-4597-9ce5-6dc7ba0a591d
📒 Files selected for processing (12)
.github/workflows/ci.yamlGLOSSARY.txtcommands/explain/README.mdpackage.jsonscripts/explain-operator-census.tsscripts/explain-operator-slice.tsscripts/probes/AGENTS.mdscripts/probes/explain-operator-sweep.tssrc/explain/operators.tstest/fixtures/explain/operators.jsontest/unit/explain-operator-probe.test.tstest/unit/explain-operators.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: copilot-pull-request-reviewer
🧰 Additional context used
📓 Path-based instructions (8)
**/AGENTS.md
📄 CodeRabbit inference engine (AGENTS.md)
Use directory-level
AGENTS.mdfiles only for local constraints.
Files:
scripts/probes/AGENTS.md
{docs/**,.github/instructions/**,.github/**/*.yml,.github/**/*.yaml,**/*.{md,txt,dict}}
📄 CodeRabbit inference engine (AGENTS.md)
Run
bun run lint:ciwhen changing documentation, instructions, security configuration, spelling dictionaries, or workflow files.
Files:
scripts/probes/AGENTS.md.github/workflows/ci.yamlGLOSSARY.txtcommands/explain/README.md
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Transport or RouterOS-touching code is not done untilbun run test:integrationpasses.
Do not silently fall back to another protocol when the caller pinned--via.
Do not make generated output the hand-edited source of truth.
Do not disable validation to make a test pass; validation is part of the product.
Files:
test/unit/explain-operator-probe.test.tstest/unit/explain-operators.test.tsscripts/explain-operator-slice.tssrc/explain/operators.tsscripts/explain-operator-census.tsscripts/probes/explain-operator-sweep.ts
test/fixtures/**/*
📄 CodeRabbit inference engine (test/AGENTS.md)
Keep fixtures under
test/fixtures/with clear source/provenance notes.
Files:
test/fixtures/explain/operators.json
src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (src/AGENTS.md)
src/**/*.{ts,tsx}: Use Bun-native TypeScript and Web APIs where possible
Errors must be actionable for humans and agents, with next-step guidance when a dependency, protocol, credential, or validation source is missing
Files:
src/explain/operators.ts
commands/*/README.md
📄 CodeRabbit inference engine (commands/AGENTS.md)
commands/*/README.md: Eachcommands/<name>/README.mdis the executable specification's designed tier: document intent, flags, and behavior; include aDesigned, not implementedtable for spec-only flags; do not duplicate implemented-flag tables generated indocs/CLI.md; link todocs/CONSTITUTION.mdrather than restating constitution-wide rules; document only command-specific behavior; and keep theStatusline consistent withdocs/MATRIX.md.
Implemented flags must be generated fromCliCommandMetadataintodocs/CLI.mdviabun run docs:cli; command READMEs must not maintain duplicate tables of implemented flags.
Files:
commands/explain/README.md
commands/*/{README.md,examples.md}
📄 CodeRabbit inference engine (AGENTS.md)
Read the target command's
README.mdandexamples.mdas the executable specification before writing code or tests.
Files:
commands/explain/README.md
scripts/probes/**/*.ts
📄 CodeRabbit inference engine (scripts/probes/AGENTS.md)
scripts/probes/**/*.ts: Both oracles must see the same bytes.
Import@tikoci/quickchrthrough./chr.ts.
Resolve the corpus through../corpus-fetch.ts.
An abstention is not a disagreement.
Files:
scripts/probes/explain-operator-sweep.ts
🧠 Learnings (32)
📓 Common learnings
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 224
File: commands/explain/examples.md:11-16
Timestamp: 2026-08-06T14:17:50.227Z
Learning: For the phase-1 offline explain API, offline behavior is tested through unit and fixture tests rather than CHR-backed integration tests. In `test/unit/explain-envelope.test.ts`, the CLI-spelling gate-versus-analysis behavior described by `commands/explain/examples.md` example `1b` and the resolved-menu behavior described by example `18b` have focused unit coverage. The CLI `explain` command surface is deferred and is not implemented as `src/cli/explain.ts` in this PR.
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: src/protocols/AGENTS.md:0-0
Timestamp: 2026-06-08T22:26:06.293Z
Learning: Applies to src/protocols/**/*router*{protocol,implementation,api,cli}*.{js,ts,md} : Ground protocol facts before implementation: RouterOS service/API/CLI path, auth model, default port, local tooling, validation source, security warnings, failure modes, and CHR test shape
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2026-06-16T10:51:07.659Z
Learning: Prefer RouterOS CHR integration tests through `quickchr` over complex mocks when behavior depends on RouterOS.
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-06-08T22:25:53.303Z
Learning: Preserve RouterOS syntax and semantics. Do not add high-level RouterOS configuration helpers; that boundary is fixed in `docs/CONSTITUTION.md`
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 0
File: :0-0
Timestamp: 2026-06-26T01:58:28.177Z
Learning: For the btest CHR integration coverage in this repository, UDP client `receive` and `both` modes are now gated; the remaining unproven UDP edge is the server cell's host→guest direction, which would require UDP host forwarding and remains documented as an open caveat.
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 227
File: src/explain/args.ts:151-158
Timestamp: 2026-08-06T23:42:39.898Z
Learning: For the RouterOS explain argument lexer corpus checks, a green frozen corpus measures coverage and risk but does not validate lexical rules absent from the corpus. The frozen corpus contains no lone-`\r` continuation case, so edge-case probes and targeted regression tests remain required for continuation behavior.
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: commands/AGENTS.md:0-0
Timestamp: 2026-07-23T00:15:08.643Z
Learning: Route RouterOS `add`, `set`, and `remove` operations through `execute`, with protocol selection governed by the constitution.
📚 Learning: 2026-08-06T14:17:50.227Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 224
File: commands/explain/examples.md:11-16
Timestamp: 2026-08-06T14:17:50.227Z
Learning: For the phase-1 offline explain API, offline behavior is tested through unit and fixture tests rather than CHR-backed integration tests. In `test/unit/explain-envelope.test.ts`, the CLI-spelling gate-versus-analysis behavior described by `commands/explain/examples.md` example `1b` and the resolved-menu behavior described by example `18b` have focused unit coverage. The CLI `explain` command surface is deferred and is not implemented as `src/cli/explain.ts` in this PR.
Applied to files:
scripts/probes/AGENTS.mdpackage.jsontest/unit/explain-operator-probe.test.tstest/unit/explain-operators.test.tsscripts/explain-operator-slice.tssrc/explain/operators.tscommands/explain/README.mdscripts/explain-operator-census.tsscripts/probes/explain-operator-sweep.ts
📚 Learning: 2026-08-11T17:46:27.539Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 273
File: test/fixtures/explain/corpus-partition.json:4-4
Timestamp: 2026-08-11T17:46:27.539Z
Learning: For durable fixtures in this repository, do not cite in-flight `.scratch/` files as provenance. In `test/fixtures/explain/corpus-partition.json`, the committed groups are the durable artifact, and the `frozen` no-regeneration statement replaces the former `.scratch/explain-lab-partition.ts` generator citation.
Applied to files:
scripts/probes/AGENTS.md.github/workflows/ci.yamltest/unit/explain-operators.test.tsscripts/explain-operator-census.ts
📚 Learning: 2026-08-05T20:06:21.299Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 217
File: src/explain/symbols.ts:0-0
Timestamp: 2026-08-05T20:06:21.299Z
Learning: In `src/explain/symbols.ts`, comparative corpus measurements that explain a semantic modeling trade-off are rule provenance and follow the established F1/F2 documentation convention. Treat run-specific bookkeeping, fuzz counts, scratch-script paths, method pointers, and per-version missed-tail details as status data that should not remain in source comments.
Applied to files:
scripts/probes/AGENTS.mdpackage.jsontest/unit/explain-operator-probe.test.tstest/unit/explain-operators.test.tsscripts/explain-operator-slice.tssrc/explain/operators.tscommands/explain/README.mdscripts/explain-operator-census.tsscripts/probes/explain-operator-sweep.ts
📚 Learning: 2026-08-05T03:19:54.280Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 209
File: test/unit/explain-menus.test.ts:146-152
Timestamp: 2026-08-05T03:19:54.280Z
Learning: In `test/unit/explain-write.test.ts`, keep separate test anchors for `isDanglingBarePath` and menu-table rejection. Known menu paths such as `/ip/firewall/filter` and `/log` reach `isDanglingBarePath` at document end, while `/system/reboot` is rejected earlier by `isConfirmedNav` because `isMenuPath` returns false. Do not combine these inputs in one test when asserting coverage of either mechanism.
Applied to files:
scripts/probes/AGENTS.mdpackage.jsontest/unit/explain-operator-probe.test.tstest/unit/explain-operators.test.ts
📚 Learning: 2026-08-06T05:38:13.259Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 222
File: test/fixtures/explain/defects.json:3-73
Timestamp: 2026-08-06T05:38:13.259Z
Learning: In `src/explain/pathresolve.ts`, `Loc.base = -1` widening applies to defects raised during nested walker traversal, such as `over-depth`, when their local offsets cannot be mapped safely from UTF-16 string indexes to analyzed UTF-8 byte offsets. Defects emitted by the top-level segmenter are already in analyzed-document byte space and therefore retain precise offsets even when their enclosing statement contains non-ASCII text. The behavior is covered by `test/fixtures/explain/defects.json` and `test/unit/explain-defects.test.ts`.
Applied to files:
scripts/probes/AGENTS.mdscripts/explain-operator-slice.tssrc/explain/operators.ts
📚 Learning: 2026-08-06T05:38:01.223Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 222
File: src/explain/defects.ts:1-70
Timestamp: 2026-08-06T05:38:01.223Z
Learning: In the TypeScript `src/explain` modules, references to phase-0 lab questions and issue numbers are an established documentation idiom when they explain the technical basis for a current contract. Keep these rationale references when they support implementation behavior. Remove dated decisions, delivery status, and future roadmap text from implementation and test comments.
Applied to files:
scripts/probes/AGENTS.mdtest/unit/explain-operators.test.tscommands/explain/README.md
📚 Learning: 2026-08-06T05:37:57.342Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 222
File: src/explain/pathresolve.ts:895-901
Timestamp: 2026-08-06T05:37:57.342Z
Learning: In `src/explain/pathresolve.ts`, over-depth defects must use `regionIn(loc, start, end)` rather than `spanIn` when the scanned extent can be empty. `regionIn` widens empty bracket or block-body extents to the enclosing statement span, preserving the `Defect` contract that `end > start` without fabricating a precise byte location.
Applied to files:
scripts/probes/AGENTS.mdsrc/explain/operators.ts
📚 Learning: 2026-08-01T23:46:54.392Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 197
File: src/explain/verbsplit.ts:383-387
Timestamp: 2026-08-01T23:46:54.392Z
Learning: In `src/explain/verbsplit.ts`, `VerbSplit` is the ratified Q6 boundary shape. Do not add document-scale context-certainty metadata to it independently; add that signal with the planned `ambiguous`/`unknown` verdict-vocabulary and phase-1 envelope work tracked in GitHub issue `#192`. Until then, `resolveVerbs` may correctly return root-based resolved paths for context-independent statements after context loss, but callers cannot observe that certainty was lost.
Applied to files:
scripts/probes/AGENTS.md
📚 Learning: 2026-08-06T23:42:39.897Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 227
File: src/explain/args.ts:151-158
Timestamp: 2026-08-06T23:42:39.897Z
Learning: In the RouterOS explain lexer, `src/explain/args.ts` and `src/explain/verbsplit.ts` must use the shared `continuationLength(text, at)` helper for backslash-newline handling. Only `\n` and `\r\n` are continuations. A lone `\r` after `\` must not be skipped; `args.ts` must refuse it as an invalid escape to preserve source-accurate token names and spans.
Applied to files:
scripts/probes/AGENTS.mdscripts/explain-operator-slice.tscommands/explain/README.mdscripts/probes/explain-operator-sweep.ts
📚 Learning: 2026-08-10T23:41:13.189Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 256
File: src/explain/args.ts:936-945
Timestamp: 2026-08-10T23:41:13.189Z
Learning: In `src/explain/args.ts`, `pushArrayMembers` must withdraw the enclosing array shape with `DEPTH_BOUND_REACHED` when `depth >= MAX_MEMBER_DEPTH`; returning `null` incorrectly reports the unverified literal as a valid array. CHR 7.23.3 accepts valid array nesting to at least depth 64, while invalid `(1,)` members remain syntax errors at all tested depths. The 948-script corpus has a maximum observed array-member depth of 6, so the depth-8 analysis bound does not drop observed valid corpus literals.
Applied to files:
scripts/probes/AGENTS.mdtest/unit/explain-operator-probe.test.tsscripts/explain-operator-slice.tssrc/explain/operators.tscommands/explain/README.mdscripts/explain-operator-census.ts
📚 Learning: 2026-08-02T03:18:27.819Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 200
File: src/explain/symbols.ts:525-535
Timestamp: 2026-08-02T03:18:27.819Z
Learning: In `src/explain/symbols.ts`, RouterOS accepts both `:onerror NAME` and bare `onerror NAME` as error-variable directives. Both forms bind `NAME` as `local`; do not require the `:` sigil for `ERRVAR_HEADS`. This matches `src/explain/blocks.ts` `DIRECTIVE_BODY` and fixture `F3a` in `test/fixtures/explain/symbols.json`, which records CHR 7.23.2 `/console/inspect request=highlight` evidence.
Applied to files:
scripts/probes/AGENTS.mdtest/fixtures/explain/operators.jsontest/unit/explain-operators.test.tscommands/explain/README.md
📚 Learning: 2026-06-16T10:51:07.659Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2026-06-16T10:51:07.659Z
Learning: Applies to test/**/cli-smoke.test.ts : Network-free CLI smoke tests (`cli-smoke.test.ts`) should not be CHR-gated and should run in the fast push/PR gate (`bun test`).
Applied to files:
.github/workflows/ci.yaml
📚 Learning: 2026-08-11T03:49:52.355Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 265
File: .github/workflows/ci.yaml:171-172
Timestamp: 2026-08-11T03:49:52.355Z
Learning: In this repository's GitHub Actions workflows, SHA-pin `oven-sh/setup-bun` and `actions/setup-node` references and include version comments. Other first-party `actions/*` references may continue using floating major-version tags according to the repository's current convention. Do not request SHA pinning for those first-party actions unless a repository-wide maintainer policy and corresponding Dependabot update policy have been established.
Applied to files:
.github/workflows/ci.yaml
📚 Learning: 2026-08-09T05:13:01.339Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 0
File: :0-0
Timestamp: 2026-08-09T05:13:01.339Z
Learning: RouterOS `.` is an expression concatenation operator with array-distributing semantics. In the current offline value-shape phase, the strict token reader refuses parenthesized, braced, and array forms before `valueShapeHints` runs. Future `observedType` and expression parsing must not treat `a.b` or `1.2` as a single atomic literal without expression-aware parsing.
Applied to files:
GLOSSARY.txttest/unit/explain-operators.test.tscommands/explain/README.md
📚 Learning: 2026-08-11T13:12:33.293Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 268
File: src/explain/args.ts:0-0
Timestamp: 2026-08-11T13:12:33.293Z
Learning: In `src/explain/args.ts`, RouterOS brace-array member keys use the measured grammar `^[A-Za-z0-9./-]+$`: `.`, `-`, and `/` are valid anywhere, including alone or repeated. Examples include `{.=1}`, `{..id=1}`, `{-=1}`, `{--=1}`, `{/=1}`, and `{a/b=1}`. `_` is not valid in this key grammar: `{_a=1}` and `{a_b=1}` are comparisons. Key binding still requires the key to touch `=`; `{.id =1}` is a `bool` comparison, while `{.id=1}` binds key `.id`. When a spelling is not recognized as a key, `pushArrayMembers()` must abstain unless `NOT_IN_MEMBER_NAME` positively proves an expression byte.
Applied to files:
GLOSSARY.txttest/fixtures/explain/operators.jsontest/unit/explain-operators.test.tsscripts/explain-operator-slice.tssrc/explain/operators.tscommands/explain/README.md
📚 Learning: 2026-08-11T12:52:27.150Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 268
File: src/explain/args.ts:896-896
Timestamp: 2026-08-11T12:52:27.150Z
Learning: In RouterOS array literals, dotted keys such as `.id` and `.id111` are valid keys. For example, `{.id=1}` stores the value under the key `".id"`; access must quote the dotted key (`$a->".id"`), while `$a->.id` does not access that key. The `memberKey()` parser in `src/explain/args.ts` must accept this dotted-key spelling.
Applied to files:
GLOSSARY.txttest/unit/explain-operators.test.tssrc/explain/operators.ts
📚 Learning: 2026-08-07T16:47:13.907Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-08-07T16:47:13.907Z
Learning: Applies to {docs/**,.github/instructions/**,.github/**/*.yml,.github/**/*.yaml,**/*.{md,txt,dict}} : Run `bun run lint:ci` when changing documentation, instructions, security configuration, spelling dictionaries, or workflow files.
Applied to files:
package.json
📚 Learning: 2026-08-07T16:47:13.907Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-08-07T16:47:13.907Z
Learning: Run `bun run lint && bun run test && bun run build` before finishing code changes.
Applied to files:
package.json
📚 Learning: 2026-08-08T00:38:33.949Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 234
File: .claude/settings.json:0-0
Timestamp: 2026-08-08T00:38:33.949Z
Learning: In `.claude/settings.json`, the `Bash(biome *)`, `Bash(tsc *)`, `Bash(cspell *)`, `Bash(markdownlint-cli2 *)`, `Bash(secretlint *)`, and `Bash(shellcheck *)` permissions intentionally allow valid lint command variants. `Bash(bun *)` already permits the repository’s `bun run lint*` commands. Do not request narrowing only these direct lint-tool permissions unless the broader Bun permission model also changes.
Applied to files:
package.json
📚 Learning: 2026-08-06T21:57:06.372Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 226
File: test/integration/cli-smoke.test.ts:333-347
Timestamp: 2026-08-06T21:57:06.372Z
Learning: For the `centrs explain` CLI, ambient stdin is read only when no positional argument can provide the offline input. When a positional input is present and stdin is supplied, the explain result includes the `usage/stdin-ignored` warning. In `test/integration/cli-smoke.test.ts`, no-input coverage must test both child stdin shapes: no `stdin` option (`/dev/null`) and `stdin: ""` (an empty pipe).
Applied to files:
test/unit/explain-operator-probe.test.ts
📚 Learning: 2026-08-09T00:35:51.834Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 241
File: src/explain/transport.ts:0-0
Timestamp: 2026-08-09T00:35:51.834Z
Learning: In `src/explain/transport.ts`, offline `print where` transport classification must fail closed unless each query expression is an attribute token with a valid property name and a non-empty decoded value. The runtime-exercised Q8 shape is `name=value`; bare property names, empty values, infix comparisons such as `address>1.1.1.1`, REST comparison words such as `>name=value`, and repeated `where` tokens must not produce a REST `.query` request.
Applied to files:
test/unit/explain-operator-probe.test.tscommands/explain/README.mdscripts/probes/explain-operator-sweep.ts
📚 Learning: 2026-08-09T05:23:19.794Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 242
File: src/explain/values.ts:83-90
Timestamp: 2026-08-09T05:23:19.794Z
Learning: In `src/explain/values.ts`, RouterOS time literals are order-independent and additive on RouterOS 7.23.3 and 7.24rc3. The `isTimeShape` function must use full fragment coverage rather than enforce descending time-unit order or reject repeated units. Examples such as `1s1m`, `1m1m`, `1h1h1h`, and `1s1ms` are valid `time` values.
Applied to files:
test/unit/explain-operators.test.tsscripts/explain-operator-slice.tssrc/explain/operators.tsscripts/probes/explain-operator-sweep.ts
📚 Learning: 2026-06-16T10:51:07.659Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: test/AGENTS.md:0-0
Timestamp: 2026-06-16T10:51:07.659Z
Learning: Applies to test/integration/**/*.test.{ts,js} : Put long-running, RouterOS-backed, or platform-specific tests (including process-level tests that spawn the real `src/cli.ts` through `cli-process.ts` and network-free CLI smoke tests in `cli-smoke.test.ts`) under `test/integration/` and wire them through QA or lab workflows.
Applied to files:
test/unit/explain-operators.test.ts
📚 Learning: 2026-08-07T16:47:13.907Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-08-07T16:47:13.907Z
Learning: Applies to **/*.{ts,tsx} : Transport or RouterOS-touching code is not done until `bun run test:integration` passes.
Applied to files:
test/unit/explain-operators.test.ts
📚 Learning: 2026-06-08T22:26:06.293Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: src/protocols/AGENTS.md:0-0
Timestamp: 2026-06-08T22:26:06.293Z
Learning: Applies to src/protocols/**/*router*{protocol,implementation,api,cli}*.{js,ts,md} : Ground protocol facts before implementation: RouterOS service/API/CLI path, auth model, default port, local tooling, validation source, security warnings, failure modes, and CHR test shape
Applied to files:
test/unit/explain-operators.test.tssrc/explain/operators.tsscripts/probes/explain-operator-sweep.ts
📚 Learning: 2026-08-06T23:42:39.898Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 227
File: src/explain/args.ts:151-158
Timestamp: 2026-08-06T23:42:39.898Z
Learning: For the RouterOS explain argument lexer corpus checks, a green frozen corpus measures coverage and risk but does not validate lexical rules absent from the corpus. The frozen corpus contains no lone-`\r` continuation case, so edge-case probes and targeted regression tests remain required for continuation behavior.
Applied to files:
test/unit/explain-operators.test.ts
📚 Learning: 2026-08-06T05:37:57.693Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 222
File: src/explain/defects.ts:1-70
Timestamp: 2026-08-06T05:37:57.693Z
Learning: In TypeScript modules under src/explain, retain references to phase-0 lab questions and issue numbers when they document the technical rationale for current implementation behavior or contracts. Remove comments containing dated decisions, delivery status, or future roadmap information when it does not explain current behavior, including in tests.
Applied to files:
src/explain/operators.ts
📚 Learning: 2026-06-08T22:25:53.303Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-06-08T22:25:53.303Z
Learning: Preserve RouterOS syntax and semantics. Do not add high-level RouterOS configuration helpers; that boundary is fixed in `docs/CONSTITUTION.md`
Applied to files:
commands/explain/README.md
📚 Learning: 2026-08-06T22:07:13.134Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 226
File: commands/explain/README.md:0-0
Timestamp: 2026-08-06T22:07:13.134Z
Learning: For `centrs explain` documentation, an instructed remedy must be executable in the current phase. The live `centrs explain <router> --file -` form is parsed but returns `usage/not-implemented` until phase 2, so documentation must present the offline `--file -` form as the actionable stdin remedy.
Applied to files:
commands/explain/README.md
📚 Learning: 2026-08-09T00:35:54.042Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 241
File: src/explain/transport.ts:0-0
Timestamp: 2026-08-09T00:35:54.042Z
Learning: In `tikoci/centrs` `src/explain/transport.ts`, the offline explain transport contract treats the Q8-probed `run(action) → POST /rest/<path>/<command>` result as a general menu-action URL rule. Verbs outside literal `add`, `get`, `set`, `remove`, `print`, and `find` classify as `api-candidate` action POSTs when their arguments contain only attributes. The action endpoint preserves the verb's original case. `find`, positional action operands, and action queries remain `unknown`.
Applied to files:
commands/explain/README.md
📚 Learning: 2026-08-07T16:47:13.907Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-08-07T16:47:13.907Z
Learning: A feature is done only when its examples pass on real CHR via `bun run test:integration`; existing code and passing unit tests alone are insufficient.
Applied to files:
scripts/probes/explain-operator-sweep.ts
🪛 ast-grep (0.45.1)
scripts/probes/explain-operator-sweep.ts
[warning] 798-805: Avoid logging sensitive data
Context: console.log(
${TAG[row.verdict]} ${row.token.padEnd(6)} +
arities=${row.operatorArities.join(",") || "-"} .padEnd(14) +
heads=${row.ilHeads.join(",") || "-"} .padEnd(14) +
hl=${(row.highlight.run?.class ?? "-").padEnd(20)} +
run=${JSON.stringify(row.highlight.run?.text ?? "")} +
${row.highlight.runIsTokenExactly ? "" : " (run != token)"},
)
Note: [CWE-532] Insertion of Sensitive Information into Log File.
(log-sensitive-data-typescript)
[warning] 875-877: Avoid logging sensitive data
Context: console.log(
${token.padEnd(6)} outer ${String(outerCount.get(token) ?? 0).padStart(3)}/${seen},
)
Note: [CWE-532] Insertion of Sensitive Information into Log File.
(log-sensitive-data-typescript)
[warning] 946-951: Avoid logging sensitive data
Context: console.log(
${verdict.padEnd(16)} ${rows .filter((r) => r.verdict === verdict) .map((r) => r.token) .join(" ")},
)
Note: [CWE-532] Insertion of Sensitive Information into Log File.
(log-sensitive-data-typescript)
🪛 OpenGrep (1.26.0)
scripts/explain-operator-census.ts
[ERROR] 405-410: SQL query built via string concatenation or template literal passed to query()/execute(). Use parameterized queries instead.
(coderabbit.sql-injection.raw-query-concat-js)
scripts/probes/explain-operator-sweep.ts
[ERROR] 68-68: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
[ERROR] 203-203: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🔇 Additional comments (31)
GLOSSARY.txt (1)
696-700: LGTM!commands/explain/README.md (1)
1043-1101: LGTM!Also applies to: 1103-1140, 1142-1187, 1189-1193
package.json (1)
67-67: LGTM!Also applies to: 100-104, 108-108
.github/workflows/ci.yaml (1)
160-164: LGTM!Also applies to: 215-228
scripts/probes/AGENTS.md (1)
23-23: LGTM!scripts/probes/explain-operator-sweep.ts (10)
67-93: LGTM!
215-277: LGTM!
300-316: LGTM!
327-454: LGTM!
463-661: LGTM!
705-748: LGTM!
750-806: LGTM!
808-883: LGTM!
885-962: LGTM!
284-290: 🎯 Functional CorrectnessConfirm highlight CSV behavior on RouterOS 7.23.3 and 7.24rc4. Existing captures for 7.23.2 and 7.24rc2 contain no empty class fields. The positional bug is conditional: an interior empty field would misalign
coalesce, but current evidence does not show that either target version emits one. Add captures or a regression fixture before changing this parser.scripts/explain-operator-census.ts (6)
72-81: LGTM!
142-196: LGTM!
208-287: LGTM!
319-346: LGTM!
355-385: LGTM!
454-461: LGTM!scripts/explain-operator-slice.ts (3)
225-225:outerCountstoresnullfor a never-outer spelling such as->, which collides with the prefix-only!/any"not measured" meaning. Store0instead.
255-277:versionDifferencesdiffs onlyruntimeandverdict. Precedence, associativity, andopAxisdifferences do not surface, so the "identical except one runtime row" claim is not enforced.
162-188: LGTM!test/fixtures/explain/operators.json (2)
1455-1472:exampleis the first carrier's IL, not the carrier that justifieswhyNot. Theevlrow and the$row both showsyntax error ...next to a residual-variable or juxtaposition verdict.
1020-1307: LGTM!src/explain/operators.ts (2)
121-173: LGTM!
403-411: LGTM!test/unit/explain-operator-probe.test.ts (1)
140-169: LGTM!test/unit/explain-operators.test.ts (2)
78-138: LGTM!
251-258: LGTM!
Works the PR #287 review from muse, Copilot and CodeRabbit. Two review items were the same defect — a claim published without evidence a reader could reach — so both are fixed by measuring, not by softening the words. Device work (three CHRs, one per release channel): - adds 7.21.5 long-term as a third capture. A test comment claimed "7.21.5 long-term still answers `any`" while asserting a row from the primary capture; the fixture held no 7.21.5 evidence at all. - adds the unary-vs-binary sweep: every `(U 1 B 2)` and `(1 B U 2)` for U in !/any/~/-/>, 240 probes. The README asserted this lattice from a local run no capture held. Measured: 240/240 accepted, binary outer in every one, identical on all three versions. - completes the dot-spacing matrix. New finding: `(1. 2)` is NOT concat — `1.` lexes as a variable name and the row is juxtaposition, `( $1. 2)`. `(1 .2)` and `(1 . 2)` are both `(. 1 2)`. - re-cuts all three captures from the committed probe, so the fixture is reproducible from source. It was not: the probe said `bogus-symbol` and the fixture said `bogus-punct`. Slice: - diffs every published axis per version, not just runtime and verdict. Mutation-tested: 4 planted mutations produce 11 rows across all 6 kinds. "Identical except one runtime row" is now measured. - `outerCount` separates measured-never-outer (`->` = 0) from never-measured (prefix-only = null); they were both null. - a non-operator's `example` is now the carrier that EARNS its verdict. `evl` showed a syntax error next to a residual-variable verdict. - validates capture shape and names the fixture prerequisite instead of failing with an iteration TypeError or a bare ENOENT. - derives the prefix-only count in the generated prose (#260). Census: - escapes `|` in head cells. IL carries unquoted string content into head position (`num|str`), which split those rows in the job summary. - `--check` refuses a corpus that is not the pinned snapshot rather than warning and returning 0. Docs/tests: word-shaped count 11 -> 18; `deviceSource` no longer claims to be the whole console command; `[` named in UNDOCUMENTED; unit coverage for the slice derivations and a verdictOf null-arity case. Not changed: `explainCommand` output is byte-identical to main on six baselines. Still grounding-only. Rejected: Copilot's two `\|` reports. GFM consumes the escape inside a code span — verified against GitHub's renderer, which emits <code>| </code>. Removing it would break the table rows instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review worked — pushed as
|
|
@coderabbitai review the latest diff, and resolve old threads if fixed |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
repo="tikoci/centrs"
pr_number="287"
printf '%s\n' '== PR head and changed files =='
gh pr view "$pr_number" --repo "$repo" --json headRefOid,baseRefOid,updatedAt,files \
--jq '{headRefOid, baseRefOid, updatedAt, files: [.files[] | {path, additions, deletions}]}'
printf '%s\n' '== Authenticated account =='
gh api user --jq '{login, id}'
printf '%s\n' '== Unresolved review threads =='
gh api graphql \
-F owner="${repo%%/*}" \
-F name="${repo#*/}" \
-F number="$pr_number" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
path
line
originalLine
comments(first: 30) {
nodes {
id
author { login }
body
createdAt
url
}
}
}
}
}
}
}' \
--jq '.data.repository.pullRequest.reviewThreads.nodes
| map(select(.isResolved == false))
| map({
id, path, line, originalLine,
comments: [.comments.nodes[] | {author: (.author.login // "ghost"), body, url}]
})'Length of output: 2845 🏁 Script executed: #!/bin/bash
set -u
repo="tikoci/centrs"
pr_number="287"
printf '%s\n' '== Diff checks =='
git diff --check 2f3df397ea21285f7d576f27f55cfb8591cb0996 0db913b3e2da2bfe6ed79e04601a34a8bddded43 || true
git diff --stat 2f3df397ea21285f7d576f27f55cfb8591cb0996 0db913b3e2da2bfe6ed79e04601a34a8bddded43
printf '%s\n' '== Changed TypeScript outlines =='
ast-grep outline scripts/explain-operator-census.ts --items all --view expanded 2>&1 || true
ast-grep outline scripts/explain-operator-slice.ts --items all --view expanded 2>&1 || true
ast-grep outline scripts/probes/explain-operator-sweep.ts --items all --view expanded 2>&1 || true
ast-grep outline src/explain/operators.ts --items all --view expanded 2>&1 || true
printf '%s\n' '== Unresolved review threads =='
gh api graphql \
-F owner="${repo%%/*}" \
-F name="${repo#*/}" \
-F number="$pr_number" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) {
nodes {
id
isResolved
path
line
originalLine
comments(first: 30) {
nodes {
author { login }
body
url
}
}
}
}
}
}
}' \
--jq '.data.repository.pullRequest.reviewThreads.nodes
| map(select(.isResolved == false))
| map({
id, path, line, originalLine,
comments: [.comments.nodes[] | {author: (.author.login // "ghost"), body, url}]
})' 2>&1 || trueLength of output: 19671 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Prior finding implementations =='
sed -n '120,180p' src/explain/operators.ts
sed -n '200,390p' scripts/explain-operator-slice.ts
sed -n '395,445p' scripts/probes/explain-operator-sweep.ts
sed -n '515,630p' scripts/explain-operator-slice.ts
printf '%s\n' '== Generated Markdown pipe rows =='
rg -n -C2 --fixed-strings '\|' commands/explain/README.md
printf '%s\n' '== Tests that pin prior findings =='
rg -n -C4 -e "never outer" -e "never measured" -e "version difference" \
-e "highlight run wider" -e "EARNS the verdict" \
test/unit/explain-operators.test.ts test/unit/explain-operator-probe.test.ts
printf '%s\n' '== Diff: workflow and package gates =='
git diff --unified=80 2f3df397ea21285f7d576f27f55cfb8591cb0996 0db913b3e2da2bfe6ed79e04601a34a8bddded43 \
-- .github/workflows/ci.yaml package.json
printf '%s\n' '== File-system and process side effects in new scripts =='
rg -n -C3 -e '\b(writeFileSync|unlinkSync|rmSync|execSync|spawn|Bun\.spawn|Database)\b' \
scripts/explain-operator-census.ts scripts/explain-operator-slice.ts scripts/probes/explain-operator-sweep.tsLength of output: 40222 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Complete version-difference implementation =='
sed -n '350,510p' scripts/explain-operator-slice.ts
printf '%s\n' '== Version-difference and fixture tests =='
sed -n '360,465p' test/unit/explain-operators.test.ts
sed -n '250,370p' test/unit/explain-operator-probe.test.ts
printf '%s\n' '== Operator registry serialization =='
sed -n '360,470p' src/explain/operators.ts
printf '%s\n' '== Current sweep difference data =='
python3 - <<'PY'
import json
path = "test/fixtures/explain/operators.json"
with open(path, encoding="utf-8") as f:
fixture = json.load(f)
sweep = fixture.get("sweep", {})
print(json.dumps({
"versions": sweep.get("_source", {}).get("versions"),
"versionDifferences": sweep.get("versionDifferences"),
"unary": sweep.get("unary"),
}, indent=2))
PYLength of output: 19553
The earlier fixes are present and test-covered. This includes the I found no unresolved CodeRabbit-authored thread. The remaining threads belong to 🧠 Learnings usedYou are interacting with an AI system. |
CodeRabbit's follow-up on #287: `controls` was published in the `sweep` block and never compared across versions, so a version-specific control acceptance or IL change stayed absent from `versionDifferences`. That is the worst axis to miss. The controls calibrate the whole sweep — `(1 zzz 2)` is the known-failing control — so a build where one moved invalidates every other row taken from it. Reporting that as "identical" is exactly the failure the diff exists to prevent. - adds an id-keyed control diff carrying `source`, `il` and `accepted`, including a control missing from a version. - restates the rule mechanically in the comment: anything the `sweep` block publishes gets diffed here. - adds three `buildSweep` tests. Mutation-checked both ways: deleting the new block turns the two positive cases red, restoring it turns them green. No fixture change — all three captures still agree on every control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/explain/operators.ts:280
- The field comment claims
highlightisvariable-undefinedfor every word innonOperators(), but this file explicitly recordsevlwithdeviceHighlightClass: "syntax-meta"(and other spellings here are punctuation). This makes the comment misleading for readers and future maintainers.
export interface NonOperator {
spelling: string;
reason: NonOperatorReason;
/** What `highlight` called it. `variable-undefined` for every word here. */
deviceHighlightClass: string;
}
scripts/probes/explain-operator-sweep.ts:133
- This docstring says “None of the candidate carriers contain a
$”, but the sweep does include candidates/sources that legitimately contain$(e.g. the$candidate and op-axis rows like(>$x)). TheresidualVariablefield is actually computed relative to each row’ssource, so the comment should reflect that to avoid readers assuming$can never appear in inputs.
/**
* The device invented a `$` the source did not have.
*
* None of the candidate carriers contain a `$`, so any `$` in the IL means
* part of the spelling was lexed as a VARIABLE REFERENCE rather than as an
Grounding-only step for #255 — sweep the RouterOS operator surface rather than transcribe the manual (same shape as #252).
What this does
scripts/explain-operator-census.ts, no CHR) as candidate generator:parseIL (naming oracle) +highlightstructure-vs-word only (scripts/probes/explain-operator-sweep.ts)src/explain/operators.ts+test/fixtures/explain/operators.json(scripts/explain-operator-slice.ts)commands/explain/README.mdoperator table; gates viaexplain:operator-census:check(corpus job) +explain:operator-readme:check(lint:ci)2*tighter+(same-1)strict weak order, 0 dropped), 3-way associativity (9 variadic flattened), plus!/anyprefix-onlyNot in this PR
No span emission, no new
ExplainSpanClass, no defect rule — #264 B2 (operator fill) + B4 (centrs→highlight projection) will readsrc/explain/operators.ts.explainCommandoutput byte-identical tomainon six baselines.Device findings
not/xor/mod/is/div/band/bor/shl/shr/eq/neare not operators ((1 not 2)→( 1 $not 2)juxtaposition)..→.+$.,//→/+$/,<>→(< 1 (> 2))re-lexed$/[/]never head a node (substitution axis)anyis an undocumented prefix operator;&&/||are alias spellings ofand/or(> x)arity 1 deferred expression vs(2 > 1)arity 2 comparison — arity is the discriminator;:typeofopvsarrayonly at runtime;<%%binds$0vsdo={}$1({2;1} > {1;2;3})errors on stable,trueon testingSee draft review comments for nits and probe gaps.
Closes #255 step 1 (grounding).
Summary by CodeRabbit
New Features
Documentation
Bug Fixes