Skip to content

README claims a pre-commit validate hook, a graph merge-conflict analyzer and a ≥80% required threshold that rootline has never had #175

Description

@pablontiv

Description

Three README claims describe engine or repository behaviour that rootline 9.12.1 does not implement, and — checked against git history — never implemented on any commit:

README Claim Reality
README.md:373 the .githooks/pre-commit hook runs validate --staged automatically the hook runs gofmt, golangci-lint and gitleaks; the string validate --staged appears in no hook, workflow or Justfile recipe
README.md:375 Rootline's graph analyzer can detect when .stem file changes conflict with document mutations no merge-conflict capability exists anywhere in the tree, and git log -S proves none ever did
README.md:393 a field present in ≥80% of records is required the comparison is strictly > (internal/infer/infer.go:113); at exactly 80% nothing is inferred

They are grouped here because they share one root cause and one fix. The root cause is that README's narrative sections were written from intent rather than re-derived from the engine — the two integration bullets landed together in e3214c2 (2026-07-22, docs(readme), README-only, +30 lines) whose own message asserts "All sections verified against current source and existing workflows", and the threshold sentence landed in 4eccab8 (2026-06-24, also docs-only). Neither claim was true at the commit that introduced it. The fix is one README pass over three lines.

Classification: documentation drift. In all three cases the binary and the repository are behaving deliberately and the sibling documentation is already correct — docs/analyze.md:51 and docs/analyze.md:107 both state >80%, and the CLI's own inference message (cmd/rootline/analyze.go:204) says appears in >80% of records — required. Only README.md is wrong.

It matters because README is the first surface an agent reads to decide what Rootline can do. An agent that believes 373 will not install a documented validation gate that does not exist; one that believes 375 will look for a graph subcommand it can never find; one that believes 393 will file a bug when a field at exactly 80% is not marked required, or will hand-write a .stem on a threshold the engine does not use.

Steps to Reproduce

1. README.md:373 — the pre-commit hook does not run validate --staged

README.md:373 claims:

  • Staged validation — The .githooks/pre-commit hook runs validate --staged automatically, catching schema violations before commit

The hook, in full, from a clean checkout at 7d95e35:

$ cat .githooks/pre-commit
#!/usr/bin/env bash
# Pre-commit: format check, lint, and secret scan

echo "Running gofmt check..."
UNFORMATTED=$(gofmt -l . 2>/dev/null | grep -v vendor || true)
...
echo "Running golangci-lint..."
if command -v golangci-lint &>/dev/null; then
  golangci-lint run
...
# Scan staged changes for secrets
if command -v gitleaks &>/dev/null; then
  gitleaks git --pre-commit --staged

--staged on line 23 belongs to gitleaks, not to rootline. Nothing in the repository invokes the flag:

$ grep -rn "validate --staged" .githooks/ .github/ .pre-commit-config.yaml Justfile
$ echo "EXIT=$?"
EXIT=1

The --staged flag itself is real (rootline validate --help lists --staged validate only files in git staging area) — what does not exist is the hook wiring README describes. The sibling bullet README.md:374 is accurate: .github/workflows/ci.yml:38 really does run ./rootline validate --all docs/roadmap/.

2. README.md:375 — no merge-conflict analysis exists on any graph surface

README.md:375 claims:

  • Merge conflict detection — Rootline's graph analyzer can detect when .stem file changes conflict with document mutations

The whole graph surface:

$ rootline graph --help
Flags:
      --check               validate only (cycles + broken links), no diagram
      --fail-cycles         treat cycles as check failures (overrides .stem links.checks.cycles)
      --format string       diagram format when -o table: dot or mermaid (default "dot")
  -h, --help                help for graph
      --quiet-cycles        suppress per-cycle enumeration when informational
      --where stringArray   filter expression (e.g. "tipo != 'feature'")

No such capability exists in the tree, and none ever did:

$ grep -rn "MergeConflict" --include="*.go" .
$ git log --oneline -S "MergeConflict" -- '*.go'
$ git log --oneline -i -S "merge conflict" -- '*.go'
$ grep -rni "merge conflict" docs/ README.md .claude/
README.md:375:- **Merge conflict detection** — Rootline's graph analyzer can detect when `.stem` ...

README.md:375 is the only occurrence of the phrase in the repository — no code, no test, no other doc page.

The nearest real thing is unrelated on both halves of the claim: internal/rules/resolver.go:180 ResolveLayered(path, root, monotonic) populates a Conflicts slice, but it compares one .stem layer against another .stem layer — never .stem against document mutations — and its only CLI reach is the monotonic-violations stem-health check inside validate --all (internal/rules/stemhealth.go:389). It is not the graph analyzer and it is not merge-conflict detection.

3. README.md:393 — the required threshold is strictly greater than 80%

README.md:392-393 claims:

Rootline's engine decides everything resolvable from form — frequency
thresholds (a field present in ≥80% of records is required) [...]

Fixture

Built from scratch in an empty scratch directory. A version 2 .stem — a stem carrying only root: true is version 0 and is rejected before inference or validation ever runs, so it would prove nothing:

.stem

version: 2
root: true
scope:
  match: "*.md"
schema:
  titulo:
    type: string
    required: true
  owner:
    type: string

Five records. owner is present in exactly four of them, which is exactly 80%. One record (control.md) deliberately omits the required titulo so the run carries a control error:

r1.md, r2.md, r3.md   ->  titulo + owner
control.md            ->  owner only        (deliberately missing required titulo)
r5.md                 ->  titulo only

Proof the fixture reaches real validation rather than stem-version rejection — the control error fired with the .stem as its source, and both stem_health and notices are empty:

$ rootline validate --all . -o json
{"errors": [{"rule": "required", "field": "titulo",
             "message": "required field \"titulo\" is missing",
             "source": "<fixture>/.stem", "severity": "error"}],
 "stem_health": [], "notices": [], "total": 5}

Run

$ rootline analyze . -o json
{"id": "required_fields", "inference_count": 0, "inferences": []}
{"id": "validation_gaps", "inference_count": 1, "inferences": [
  {"type": "required_understatement", "source": "<fixture>/.stem", "field": "owner",
   "message": "Field \"owner\" is used in 4/5 records (80%) but not declared required",
   "requires_agent": true}]}

init agrees — at exactly 80% owner is emitted with no required: true:

$ rootline init . --dry-run
version: 2
root: true
scope:
  match: "*.md"
schema:
  owner:
    type: enum
    values: [alice]
  titulo:
    type: enum
    values: [Doc 1, Doc 2, Doc 3, Doc 5]

Positive control — add one more record carrying owner, taking presence to 5/6 (83%), and the inference fires:

$ printf -- '---\ntitulo: Doc 6\nowner: alice\n---\n\n# Doc 6\n' > r6.md
$ rootline analyze . -o json
required_fields 2 ['field "owner" appears in >80% of records — required',
                   'field "titulo" appears in >80% of records — required']
validation_gaps 0 []

The engine's own message says >80%. The source agrees and always has:

// internal/infer/infer.go:112-114
// Required: present in >80% of records
if float64(stats.Count)/float64(total) > 0.8 {
    sf.Required = true
}
$ git log --oneline -S ">= 0.8" -- internal/infer/infer.go
$ git show 4eccab8:internal/infer/infer.go | grep -n "0\.8"
113:		if float64(stats.Count)/float64(total) > 0.8 {

4eccab8 is the commit that wrote ≥80% into README; at that same commit the comparison was already >. The comparison has never been >= on any commit.

Worth noting that the exact-80% case is not silent — validation_gaps reports required_understatement there, gated at >= 0.80 (internal/infer/validation_gaps.go:11). The two detectors are complementary by design; it is only README that names the wrong operator for the one that infers required.

Expected Behavior

README.md describes what the shipped engine and the shipped hooks do, so that a reader who acts on it is not chasing a hook, a subcommand, or a threshold boundary that does not exist.

Actual Behavior

  • README.md:373 describes a .githooks/pre-commit step that does not exist; the string validate --staged appears nowhere in the repository.
  • README.md:375 describes a graph capability with no code, no test, no flag and no other mention anywhere in the tree, on any commit.
  • README.md:393 states ≥80% where the engine, its own inference message, and docs/analyze.md:51,107 all say >80%; at exactly 80% nothing is inferred.

Environment

  • Rootline version: rootline version 9.12.1 (equal to origin/master at 7d95e35)
  • OS: macOS (arm64)

Related work

Labels owed by a maintainer: documentation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions