Skip to content

feat(explain): total, gapless token partition behind --tokens (#289 B1) - #291

Merged
mobileskyfi merged 5 commits into
mainfrom
feat/explain-token-partition-b1
Aug 13, 2026
Merged

feat(explain): total, gapless token partition behind --tokens (#289 B1)#291
mobileskyfi merged 5 commits into
mainfrom
feat/explain-token-partition-b1

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #289 — stage B1 of #264.

Every byte of the analyzed input now belongs to exactly one token — data.tokens[] behind --tokens (matching --complete/--schema/--curl), sorted by start, no gaps, no overlaps, join(slice)===input. Byte-exact on analyzed text so it inherits #215/#252; input.positionMap applies when normalized. class is explicitly provisional until #264 B5.

No new classification: reuses existing analyzers (comments + decided symbol occurrences); everything else is unclassified (ev: e0 provisional, B2 fills carry the claiming pass). data.spans[] untouched so nothing shipped changes without the flag.

Deliverable is a number — 27.44% of corpus bytes classified (391,474 / 1,426,731 over 948 scripts, 46,580 tokens, avg 49.1/script), reported as a generated block in commands/explain/README.md via test/fixtures/explain/tokens.jsoncorpus. Two drift gates:

  • corpus → fixture via bun run explain:token-census:check (CI corpus job, needs DB)
  • fixture → README via bun run explain:token-census:readme:check (lint:ci, offline)

Each B2 fill (path/menu, arg name/=, value/array, quotes/escapes, operators #290) will move the classified percentage.

Review fixes in this PR: wires ci.yaml corpus job, removes dead out.length===0 branch in buildTokens, annotates provisional e0.

Verification: bun run lint:typecheck / biome / cspell 0 pass, bun test test/unit/explain-token-census.test.ts 17 pass, manual invariant probe over representative inputs green.

Summary by CodeRabbit

  • New Features

    • Added an opt-in --tokens flag to the explain command.
    • Token output now provides a complete, ordered byte partition with token classes and unclassified ranges for uncovered bytes.
  • Documentation

    • Updated CLI documentation and the explain command README with token output details, coverage statistics, and partition guarantees.
    • Added glossary definitions for token-partition and source-code analysis terminology.
  • Quality Improvements

    • Added automated validation to detect token coverage, ordering, and generated documentation inconsistencies.

Implements stage B1 of #264: a new data.tokens[] facet behind --tokens,
a total partition of [0, input.bytes) where every byte belongs to exactly
one token (sorted, no gaps, no overlaps, join(slice)===input). Byte-exact
on analyzed text so it inherits #215/#252 and positionMap applies when
normalized.

No new classification — reuses existing analyzers (comments + variable
occurrences); everything else is unclassified (first-class, ev e0
provisional until B5). data.spans[] untouched so nothing shipped changes
without the flag, matching --complete/--schema/--curl pattern.

Deliverable is a number: 27.44% of corpus bytes classified (391,474 /
1,426,731 over 948 scripts, 46,580 tokens, avg 49.1/script), reported as
a generated block in commands/explain/README.md via
test/fixtures/explain/tokens.json → corpus. Census is re-derivable
with bun run explain:token-census and drift-gated at both links:

- corpus → fixture via bun run explain:token-census:check (CI corpus job)
- fixture → README via bun run explain:token-census:readme:check (lint:ci)

Each B2 fill (path/menu, arg name/=, value/array, quotes/escapes,
operators) will move the classified percentage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 12, 2026 23:33
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f969d140-4c57-4a0c-84ea-a571044f0e1c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds opt-in gapless token partitions to explain, generates corpus token census data, validates fixture and README consistency, and integrates the checks into package scripts and CI.

Changes

Token output and census

Layer / File(s) Summary
Token partition contract and construction
src/explain.ts
Defines ExplainToken and builds sorted, contiguous partitions with unclassified gaps.
CLI token-output wiring
src/cli/explain.ts, src/explain.ts
Parses --tokens and forwards the option to include data.tokens[].
Corpus census and drift validation
scripts/explain-token-census.ts, test/fixtures/explain/tokens.json, test/unit/explain-token-census.test.ts
Analyzes corpus scripts, checks partition invariants, compares census metrics, and validates generated README content.
Documentation and CI integration
package.json, .github/workflows/ci.yaml, commands/explain/README.md, docs/CLI.md, GLOSSARY.txt
Adds census commands, CI checks and summaries, CLI documentation, generated census text, and glossary terms.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🟡 Moderate · up to 8d1e2

The PR adds token partition output and corpus drift validation, but malformed ranges can still produce incorrect token reconstruction and check mode can accept an unpinned corpus with matching aggregate metrics. These bounded correctness issues should be fixed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant explainCommand
  participant buildTokens
  participant Census
  participant SQLite
  CLI->>explainCommand: run explain --tokens
  explainCommand->>buildTokens: analyze input spans
  buildTokens-->>explainCommand: return gapless data.tokens[]
  Census->>SQLite: load corpus scripts
  Census->>explainCommand: request token analysis
  explainCommand-->>Census: return token partitions
  Census-->>Census: compare fixture and README metrics
Loading

Possibly related issues

Possibly related PRs

  • tikoci/centrs#222 — Provides the byte-span analysis that this PR converts into gapless token partitions.
  • tikoci/centrs#224 — Extends the same src/explain.ts output and option flow.
  • tikoci/centrs#267 — Provides the census fixture, README generation, and CI drift-gating pattern.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the new gapless token partition and the --tokens flag.
Description check ✅ Passed The description covers the change, linked issues, validation, assumptions, and implementation scope, although it does not follow every template heading.
Linked Issues check ✅ Passed The implementation adds data.tokens[] behind --tokens, preserves spans, reuses existing classifications, validates partition invariants, and reports corpus coverage for #289.
Out of Scope Changes check ✅ Passed The code, tests, documentation, fixture, scripts, and CI changes directly support the token partition and census objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/explain-token-partition-b1

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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-token-census.ts`:
- Around line 293-297: Update the check-mode flow around resolution.warning so
that when args.includes("--check") and resolution.warning is present, it logs
the warning and returns 1 before opening the database or comparing the fixture;
preserve the existing warning behavior outside check mode.
- Around line 86-104: Update the token validation loop in the explain-token
census checks to validate every token’s start and end offsets before
reconstruction: require integer values and 0 <= start < end <= bytes. Record an
invariant failure for any invalid range and ensure invalid tokens cannot reach
the analyzed.slice reconstruction.

In `@src/explain.ts`:
- Around line 842-846: Update the span validation in buildTokens to reject
ranges where s.end is less than s.start, in addition to the existing bounds
checks. Perform this validation before updating prev so reversed spans cannot
produce invalid token ranges.
🪄 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: bfafd3aa-584a-436b-9911-950b5cca1a2e

📥 Commits

Reviewing files that changed from the base of the PR and between ca088e2 and 8d1e2a4.

📒 Files selected for processing (10)
  • .github/workflows/ci.yaml
  • GLOSSARY.txt
  • commands/explain/README.md
  • docs/CLI.md
  • package.json
  • scripts/explain-token-census.ts
  • src/cli/explain.ts
  • src/explain.ts
  • test/fixtures/explain/tokens.json
  • test/unit/explain-token-census.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: CHR smoke (stable)
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (6)
test/fixtures/**/*

📄 CodeRabbit inference engine (test/AGENTS.md)

Keep fixtures under test/fixtures/ with clear source/provenance notes.

Files:

  • test/fixtures/explain/tokens.json
{docs/**,.github/instructions/**,.github/**/*.yml,.github/**/*.yaml,**/*.{md,txt,dict}}

📄 CodeRabbit inference engine (AGENTS.md)

Run bun run lint:ci when changing documentation, instructions, security configuration, spelling dictionaries, or workflow files.

Files:

  • GLOSSARY.txt
  • docs/CLI.md
  • commands/explain/README.md
  • .github/workflows/ci.yaml
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Transport or RouterOS-touching code is not done until bun run test:integration passes.
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-token-census.test.ts
  • src/cli/explain.ts
  • scripts/explain-token-census.ts
  • src/explain.ts
commands/*/README.md

📄 CodeRabbit inference engine (commands/AGENTS.md)

commands/*/README.md: Each commands/<name>/README.md is the executable specification's designed tier: document intent, flags, and behavior; include a Designed, not implemented table for spec-only flags; do not duplicate implemented-flag tables generated in docs/CLI.md; link to docs/CONSTITUTION.md rather than restating constitution-wide rules; document only command-specific behavior; and keep the Status line consistent with docs/MATRIX.md.
Implemented flags must be generated from CliCommandMetadata into docs/CLI.md via bun 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.md and examples.md as the executable specification before writing code or tests.

Files:

  • commands/explain/README.md
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/cli/explain.ts
  • src/explain.ts
🧠 Learnings (27)
📓 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: 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.
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 287
File: scripts/explain-operator-census.ts:429-454
Timestamp: 2026-08-12T19:54:50.443Z
Learning: In the centrs census commands, a successful fixture-drift check is valid only when the resolved corpus is the pinned corpus. `scripts/explain-operator-census.ts` rejects `--check` when `resolution.warning` is set. `scripts/explain-value-census.ts` has the same warning-only resolution shape but was deliberately left unchanged in PR `#287` to keep the RouterOS operator-surface work scoped to issue `#255`.
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.
📚 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:

  • test/fixtures/explain/tokens.json
  • test/unit/explain-token-census.test.ts
  • .github/workflows/ci.yaml
  • scripts/explain-token-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:

  • test/fixtures/explain/tokens.json
  • test/unit/explain-token-census.test.ts
  • commands/explain/README.md
  • .github/workflows/ci.yaml
  • src/cli/explain.ts
  • scripts/explain-token-census.ts
  • src/explain.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-12T19:54:50.443Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 287
File: scripts/explain-operator-census.ts:429-454
Timestamp: 2026-08-12T19:54:50.443Z
Learning: In the centrs census commands, a successful fixture-drift check is valid only when the resolved corpus is the pinned corpus. `scripts/explain-operator-census.ts` rejects `--check` when `resolution.warning` is set. `scripts/explain-value-census.ts` has the same warning-only resolution shape but was deliberately left unchanged in PR `#287` to keep the RouterOS operator-surface work scoped to issue `#255`.

Applied to files:

  • package.json
  • test/unit/explain-token-census.test.ts
  • commands/explain/README.md
  • .github/workflows/ci.yaml
  • scripts/explain-token-census.ts
📚 Learning: 2026-08-01T22:09:14.680Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 195
File: GLOSSARY.txt:548-561
Timestamp: 2026-08-01T22:09:14.680Z
Learning: In this repository, CSpell accepts a digit-suffixed token such as `toip6` when its alphabetic base token, `toip`, is present in `GLOSSARY.txt`. Do not add redundant digit-suffixed glossary entries solely for CSpell coverage.

Applied to files:

  • GLOSSARY.txt
📚 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.txt
📚 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.txt
📚 Learning: 2026-08-06T14:17:38.964Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 224
File: commands/explain/README.md:0-0
Timestamp: 2026-08-06T14:17:38.964Z
Learning: For the `explain` command documentation, `commands/explain/examples.md` specifies the finished target contract, not the current implementation phase. `commands/explain/README.md` contains the phase inventory and must map omitted phase fields to the issues and examples that will make them green. Future phase gaps must add fields without changing the finished example contract.

Applied to files:

  • docs/CLI.md
  • commands/explain/README.md
📚 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:

  • docs/CLI.md
  • test/unit/explain-token-census.test.ts
  • .github/workflows/ci.yaml
  • src/cli/explain.ts
  • scripts/explain-token-census.ts
  • src/explain.ts
📚 Learning: 2026-07-23T00:15:08.643Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: commands/AGENTS.md:0-0
Timestamp: 2026-07-23T00:15:08.643Z
Learning: Unknown verbs, sub-verbs, and flags must produce an invalid-command or usage error listing the closest canonical matches using Levenshtein distance, along with accepted aliases.

Applied to files:

  • docs/CLI.md
📚 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:

  • test/unit/explain-token-census.test.ts
  • src/explain.ts
📚 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-token-census.test.ts
📚 Learning: 2026-06-11T06:18:36.306Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 13
File: commands/transfer/examples.md:5-9
Timestamp: 2026-06-11T06:18:36.306Z
Learning: In the `centrs` project (tikoci/centrs), `commands/transfer/examples.md` uses a multi-prefix example numbering scheme: `1-21` for core REST examples, `N*` for native-api, `S*` for SSH/SFTP, and `P*` for pending/gated examples. The test↔example mapping is NOT positional (not "example N → assertion N"). Instead, `test/integration/transfer.test.ts` annotates each test block with its example ID and records coverage via `recordIntegrationEvidence({ exampleIds })`. This is the intended, unambiguous mapping mechanism for this project.

Applied to files:

  • test/unit/explain-token-census.test.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:

  • test/unit/explain-token-census.test.ts
📚 Learning: 2026-08-12T19:55:32.402Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 287
File: test/unit/explain-operators.test.ts:320-360
Timestamp: 2026-08-12T19:55:32.402Z
Learning: For the RouterOS operator sweep, `test/fixtures/explain/operators.json` stores captures from RouterOS 7.21.5 long-term, 7.23.3 stable, and 7.24rc4 testing. `scripts/explain-operator-slice.ts` compares every published sweep axis, including unary precedence, across these captures and emits `sweep.versionDifferences`. The `any` operator has no version differences in these captures.

Applied to files:

  • test/unit/explain-token-census.test.ts
  • commands/explain/README.md
📚 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-token-census.test.ts
  • commands/explain/README.md
  • src/explain.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:

  • test/unit/explain-token-census.test.ts
📚 Learning: 2026-08-12T21:33:50.821Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: scripts/probes/AGENTS.md:0-0
Timestamp: 2026-08-12T21:33:50.821Z
Learning: Applies to scripts/probes/**/*.ts : **`highlight` and `:parse` are different oracles and disagree.** `highlight`
  accepts `{1;2,}`, `{2,}` and `{(1,2),}`, all of which `:parse` rejects. Say
  which one a row was scored on.

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:

  • .github/workflows/ci.yaml
📚 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-09T00:35:47.021Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 241
File: src/explain/transport.ts:0-0
Timestamp: 2026-08-09T00:35:47.021Z
Learning: In `src/explain/transport.ts`, transport input arguments must use `ExplainArguments` from `src/explain.ts`, narrowed with `Extract<ExplainArguments, { read: true }>`. Do not substitute `ArgumentsRead` from `src/explain/args.ts`: it is a distinct lexer-level type that includes `args: Record<string, string>` and uses a different span representation, while `src/explain.ts` keeps command arguments separately in `command.args`.

Applied to files:

  • src/cli/explain.ts
  • src/explain.ts
📚 Learning: 2026-08-12T21:33:50.821Z
Learnt from: CR
Repo: tikoci/centrs PR: 0
File: scripts/probes/AGENTS.md:0-0
Timestamp: 2026-08-12T21:33:50.821Z
Learning: Applies to scripts/probes/**/*.ts : **Resolve the corpus through `../corpus-fetch.ts`.** A hardcoded
  `$HOME/GitHub/lsp-routeros-ts` path is the reachability bug `#186` exists to
  fix, and the resolver announces which snapshot's bytes were measured.

Applied to files:

  • scripts/explain-token-census.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:

  • src/explain.ts
🔇 Additional comments (11)
src/explain.ts (2)

409-433: LGTM!

Also applies to: 549-559, 897-904, 1464-1467, 1522-1528


1050-1052: 🎯 Functional Correctness

Run the required integration test.

No passing bun run test:integration result is included for this RouterOS-touching TypeScript path. Run the command and resolve failures before merge.

  • src/explain.ts#L1050-L1052: validate token-enabled explanation output.
  • src/cli/explain.ts#L477-L480: validate --tokens CLI propagation.

As per coding guidelines: **/*.{ts,tsx}: Transport or RouterOS-touching code is not done until bun run test:integration passes.

Source: Coding guidelines

src/cli/explain.ts (1)

92-96: LGTM!

Also applies to: 115-115, 171-173

test/fixtures/explain/tokens.json (1)

1-28: LGTM!

test/unit/explain-token-census.test.ts (1)

50-176: LGTM!

commands/explain/README.md (1)

1202-1217: LGTM!

docs/CLI.md (1)

226-226: LGTM!

GLOSSARY.txt (1)

702-704: LGTM!

package.json (2)

67-67: 📐 Maintainability & Code Quality

Run the required documentation validation.

Run bun run lint:ci and provide the result before merge. This change includes documentation, glossary, and workflow files.

As per coding guidelines, “Run bun run lint:ci when changing documentation, instructions, security configuration, spelling dictionaries, or workflow files.”

Source: Coding guidelines


118-121: LGTM!

.github/workflows/ci.yaml (1)

161-171: LGTM!

Also applies to: 224-239

Comment thread scripts/explain-token-census.ts Outdated
Comment thread scripts/explain-token-census.ts
Comment thread src/explain.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in --tokens facet to centrs explain that emits data.tokens[] as a total, gapless byte partition of the analyzed input, and introduces a drift-gated token census (fixture + generated README block) wired into CI.

Changes:

  • Add --tokens facet and ExplainToken types; build a gapless token partition from existing spans[] plus unclassified gaps.
  • Add corpus-driven token census tooling (scripts/explain-token-census.ts), a pinned fixture (test/fixtures/explain/tokens.json), and unit tests to drift-gate README rendering.
  • Wire the census into CI (ci.yaml corpus job + lint:ci README projection gate) and update glossary/CLI docs.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/explain.ts Introduces ExplainToken/data.tokens[], buildTokens(), and threads the facet through explainCommand/explainEnvelope.
src/cli/explain.ts Adds --tokens flag parsing and passes the facet into the envelope.
scripts/explain-token-census.ts New script to compute and gate token census + README block rendering from fixture/corpus.
test/unit/explain-token-census.test.ts Adds invariant tests for the token partition and drift gates for README block rendering.
test/fixtures/explain/tokens.json Pins the token census results for the corpus (the “deliverable number”).
commands/explain/README.md Adds a generated token-census block and updates the narrative around tokens/operators.
docs/CLI.md Documents the new --tokens flag in generated CLI docs.
package.json Adds token-census scripts and adds the README-projection gate to lint:ci.
GLOSSARY.txt Adds project vocabulary entries for “gapless”, “SCIP”, and “unclassified”.
.github/workflows/ci.yaml Extends the corpus census job summary and adds a token-census fixture drift gate.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/explain.ts
Comment thread test/unit/explain-token-census.test.ts
…k corpus

- src/explain.ts: validate buildTokens spans before overlap — bounds,
  non-empty (end <= start rejected), and integer checks first so a
  negative start no longer masquerades as "overlapping"

- scripts/explain-token-census.ts: validate every token range
  (integer, 0 <= start < end <= bytes) before gap/overlap and slice
  reconstruction, so negative offsets can not slip through slice()

- scripts/explain-token-census.ts: refuse --check when resolution.warning
  is set (pinned-corpus gate, matching explain-operator-census)

- test/unit/explain-token-census.test.ts: make the unclassified
  assertion stable across B2 fills (whitespace-only input + fixture
  counter) instead of pinning a specific command that will become
  fully classified

Addresses CodeRabbit #3771168524, #3771168526, #3771168531 and
Copilot #3771177335, #3771177366

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mobileskyfi

Copy link
Copy Markdown
Contributor Author

Fixed all 5 review threads in 1d28622:

  • src/explain.ts — reversed/empty spans: validate end <= start and integer checks before overlap, so negative starts no longer masquerade as "overlapping" and zero-length spans are rejected. Kept plain Error (internal invariant, not user input) with span details — matches the existing actionable pattern for this internal bug path.
  • scripts/explain-token-census.ts — token ranges: validate every token 0 <= start < end <= bytes + integer before gap/slice recon, so slice() with negative offsets can not slip through.
  • scripts/explain-token-census.ts — warning-bearing corpus: refuse --check when resolution.warning is set (pinned-corpus gate, matching explain-operator-census), preserving the warning-only path outside --check.
  • test/unit/explain-token-census.test.ts — brittle unclassified assertion: switched from pinning a command that B2 will fill to a whitespace-only partition (" " → single unclassified) plus the fixture counter, so the "unclassified is first-class" invariant is stable across fills.

Verified lint:typecheck/biome clean and bun test test/unit/explain-token-census.test.ts 17 pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/explain-token-census.ts:135

  • When the token-partition invariant fails, the thrown error does not identify which script caused the failure (or show any snippet/ID). In CI this will be hard to debug because messages like “gap/overlap at 17” aren’t actionable without knowing the input that triggered it.
	if (invariantFailures.length > 0) {
		throw new Error(
			`token partition invariant violated: ${invariantFailures.slice(0, 5).join("; ")}`,
		);

scripts/explain-token-census.ts:256

  • The classifiedPct drift check comment says “tolerance to 2 decimal places”, but the actual tolerance is 0.0001 (1e-4). This mismatch makes the drift-gate behavior harder to understand/maintain.
		// classifiedPct is derived; compare with tolerance to 2 decimal places.

mobileskyfi and others added 2 commits August 12, 2026 17:20
Three findings from an independent review of #291, all in B1's own scope:

- `--tokens` was a silent no-op in the DEFAULT text format: output was
  byte-identical with and without the flag, and only `--json`/`--yaml`
  carried the partition. Every other opt-in flag changes the text
  surface (`--curl` adds a `curl:` line; `--complete`/`--schema` emit a
  tip). Text now gains a `tokens:` section whose header is the #289
  deliverable — the per-input coverage number — followed by one row per
  token, `unclassified` runs included.

- An `unclassified` token cited `e0` (`canonicalizeExecuteCommand`). The
  pass that produced an UNCLAIMED byte is the one that produced the
  analyzed surface its offsets are on: `e1` (`analyzeCoordinates`). `EV`
  is documented as "the pass that actually produced the fact"; both ids
  are always cited, so this is attribution, not a dangling reference.

- `buildTokens` is exported and was referenced by no test. The three
  guards added in 1d28622 (non-integer, out-of-bounds/empty/reversed,
  overlap) are unreachable from `explainCommand` — 40k fuzzed inputs
  never trip them — so nothing went red if one were deleted. Added
  direct tests for each branch plus the unsorted-but-disjoint and
  empty-input paths; mutation-checked that the overlap test fails when
  the guard is stubbed out.

Also wraps the README paragraph after the generated block and records
the B2 constraint found while reviewing: `data.tokens[]`'s only fill
source is `data.spans[]`, which is the proof-only facet, so the operator
fill (#290) needs a claim seam rather than a new `spans[]` class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ExplainSpan` and `ExplainSpanClass` are public; their token counterparts
were not, so a library consumer calling `explainCommand(input, { tokens:
true })` could reach `data.tokens` but could not name its element type.
`src/AGENTS.md`: the TypeScript API is the root contract.

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

Copy link
Copy Markdown
Contributor Author

Independent review — 4 findings, all fixed on this branch

Checks were green and three bot rounds had landed, so I looked for what a
green diff hides rather than re-running the same passes. Everything below is
fixed in 41d5be3 + c7b49d2.

1. --tokens was a silent no-op in the default format

$ centrs explain ':local x 1 # note $x' --tokens > with
$ centrs explain ':local x 1 # note $x'          > without
$ diff with without      # (no output)

The partition only appeared under --json/--yaml. Every other opt-in flag
changes the text surface — --curl adds a curl: line, --complete /
--schema emit a tip — so a flag whose entire visible effect requires a
second flag reads as broken. Text now renders:

tokens: 3 token(s), 1/20 byte(s) classified (5.0%), class provisional
  [0,7)        unclassified
  [7,8)        variable-local
  [8,20)       unclassified

The header is #289's stated deliverable — the coverage number — scoped to the
one input in front of you, so the progress bar is legible without the corpus.

2. unclassified tokens cited the wrong pass

They carried ev: "e0" = canonicalizeExecuteCommand. The pass that produced
an unclaimed byte is the one that produced the analyzed surface its offsets
sit on: e1 = analyzeCoordinates — which buildTokens' own docstring already
says ("Offsets are on the analyzed text"). EV's header is explicit that an id
"has to be the pass that actually produced the fact". Both ids are always seeded
in citedEvidence, so this was never a dangling reference — just wrong
attribution, and cheapest to fix before the field ships.

3. buildTokens is exported and no test referenced it

grep -rn buildTokens src test scripts returned only the definition and its one
call site. All three guards added in 1d28622 for the CodeRabbit/Copilot round
were therefore unverified — and unreachable from explainCommand: I fuzzed
40,000 random inputs over a RouterOS-shaped alphabet ($ # " ' \ ; { } [ ] ( ) = / , . : -, newlines, é, astral, CR) plus 15 hand-built comment/symbol overlap
candidates, and got zero throws and zero partition breaks. Good news for the
invariant, bad news for the guards: nothing went red if one were deleted.

Added direct tests for each branch (non-integer, negative start, end past input,
empty and reversed, overlap in both input orders) plus the
unsorted-but-disjoint and empty-input paths. Mutation-checked: stubbing
if (s.start < prev) to if (false) turns the overlap test red.

4. ExplainToken / ExplainTokenClass were not exported from the root API

ExplainSpan and ExplainSpanClass are in src/index.ts; their token
counterparts were not. A library consumer calling explainCommand(input, { tokens: true }) could reach data.tokens but could not name its element type.

Verified, not changed

  • The partition invariant itself — 40k fuzz above, plus
    bun run explain:token-census:check against the pinned sibling corpus
    (sha256:0accd1c08743, resolved with no warning): matches the committed
    fixture
    .
  • input.bytes === analyzed.length holds because analyzed is pure ASCII by
    construction, so the census's byte/index conflation is sound, not lucky —
    and classByteCounts summing to exactly totalBytes (1,426,731) is the
    corpus-scale proof.
  • The three earlier bot findings are genuinely fixed, not just replied to.

bun run lint:ci && bun run test && bun run build green (2,745 pass / 0 fail).

One thing for #290, not for this PR

buildTokens(analyzed, spans)' only fill source is data.spans[], and
ExplainTokenClass = ExplainSpanClass | "unclassified". So B2 as currently
specified has no seam — details in a comment on #290.

Both items Copilot SUPPRESSED on its second pass, taken on merit — a
suppressed finding is still a finding.

- `gap/overlap at 17` was unactionable in CI: the corpus is not in this
  repo, so with no row index and no snippet there was nothing to go look
  at. Failures now read `script #1 ("/ip address add address=1.1.1.1 #
  a very long trailing comme"…): gap/overlap at token 1: [0,1) then
  [0,103)`. Verified by injecting a partition break and reading the
  message the CI job would print.

- The `classifiedPct` comment claimed a "tolerance to 2 decimal places"
  while the code uses 1e-4. The epsilon is not a drift allowance at all:
  `classifiedPct` is derived from `classifiedBytes` / `totalBytes`, both
  already compared exactly, so it only absorbs float round-trip noise
  through JSON — and must stay far tighter than the 2 decimal places the
  README renders. Comment now says that.

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

Copy link
Copy Markdown
Contributor Author

Addendum — Copilot's two suppressed comments, taken on merit (5a9d19e)

Copilot's second pass says "generated no new comments" on my round, but its
Suppressed comments (2) block held two real ones against B1's census script.
A suppression is not a disposition, so both are fixed.

A census invariant failure did not name its script. gap/overlap at 17 is
unactionable in CI — the corpus is not in this repo, so with no row index and no
snippet there is nothing to go look at. Verified by injecting a partition break
and reading what the job would actually print:

token partition invariant violated: script #1 ("/ip address add address=1.1.1.1 # a very long trailing comme"…):
  gap/overlap at token 1: [0,1) then [0,103); script #1 (…): join(slice) !== input

The classifiedPct comment claimed "tolerance to 2 decimal places" while the
code uses 1e-4.
The interesting part is that the comment was the wrong end
to fix: that epsilon is not a drift allowance at all. classifiedPct is derived
from classifiedBytes / totalBytes, and diffAgainstFixture already compares
both of those exactly — so any real drift is caught before the pct is
reached. The epsilon only absorbs float round-trip noise through JSON, and it has
to stay far tighter than the 2 decimal places the README block renders, or it
would start hiding drift the exact comparisons would otherwise have caught. The
comment now says that.

bun run lint && bun run test && bun run build green (2,745 pass / 0 fail), and
bun run explain:token-census:check still matches the fixture against the pinned
corpus.

@mobileskyfi
mobileskyfi merged commit 54695ff into main Aug 13, 2026
11 checks passed
@mobileskyfi
mobileskyfi deleted the feat/explain-token-partition-b1 branch August 13, 2026 00:36
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.

explain/tokens: B1 — a total, gapless token partition behind --tokens

2 participants