Normalize Windows cache paths when reporting runtime settings and resolve
Git executables before spawning history queries. Keep executable lookup
results per thread and cover the ownership contract deterministically.
Support Git 2.43 while denying history-query network transports, and close
the activity database on daemon startup failure so Windows can clean up.
Validation: 463 native macOS tests, 421 Windows tests, 44 MSan UI tests,
and focused canonical lint passed. Production-only revert checks confirm
the repaired failures.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Reconcile the complete Atlas and Lens stream with current main while retaining both histories. Preserve architecture functionality, current process and watcher safeguards, test infrastructure, and dependency security updates. Add coverage for Git-environment isolation combined with child-local overrides.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Merge validated environment overrides into a child-owned environment on POSIX and Windows, preserving caller PATH lookup for executable names. Forward each ObjectScript export parse timeout to the extractor.
Cover inherited and replaced values, empty values, duplicate overrides, parent isolation, invalid names, executable lookup, and timeout forwarding.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
On macOS the signed-candidate preparation folded every step into one
boolean, so each failure printed the same line: "signed update candidate
preparation failed". A full /tmp read exactly like a broken codesign. That
is what a cli test hit on 2026-10-09, while a multi-GB index was filling a
nearly full disk.
Each step now records why it failed, and the update prints, for example:
error: signed update candidate preparation failed while writing the
unsigned candidate: No space left on device
The staging functions in activation_transaction.c keep the errno of the
failing write (or sync, close, read) through their cleanup, so a caller can
name it. The non-macOS path adds the OS error to its existing "failed to
stage verified update" line (POSIX only; Windows reports through
GetLastError). A failed fingerprint of the staged file now gets its own
message instead of "...: activation transaction completed".
A test seam makes the next staging write fail with a chosen errno. The new
test cli_update_names_the_failing_staging_step_and_os_error fails without
the cli.c change and passes with it, and checks that the old executable
stays in place. Also checked on real input: with /tmp on a small volume
that fills between two updates, the update prints the line above.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The doc-section candidate pass (#2574) made a Linux kernel index 2.8x
slower: 207 s before #2574, 580 s after, on the same machine. Every
candidate of every section got a dot product that merged the section's up
to 1024 terms with the function's, and then every candidate was sorted for
a top 10.
- The section's vector is held densely per worker, indexed by term and
stamped per section, so a candidate's dot product walks only the
function's (or file's) own terms. The same products are added in the
same ascending term order, so every score is the same float.
- Only the candidates the results read are sorted: the top
SEM_DOC_TOP_K (and the local ones down to CBM_SEM_DOC_MIN_SCORE), the
top CBM_SEM_DOC_FILE_K files. cmp_doc_cand is a total order, so the
sorted prefix is the full sort's prefix.
Kernel (perf-bench/linux, M5 Pro, same plain build): 579.7 s -> 391.5 s,
nodes and edges unchanged. Exactness, measured directly: a build that also
ran the old merge and the old full sort found 0 score, 0 shared-term and
0 order differences over 5,003 django sections.
Candidate generation itself (a section's key terms, df <= funcs/20) is
unchanged here: narrowing it would change the stored rows.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1a342553 made suffix_match ties deterministic by preferring the least
nested QN, then the smaller QN. Determinism was the point, and it stays.
But depth says nothing about which same-named definition a call means.
An audit of main against v0.11.0 (2026-10-10) traced three resolution
regressions to that one rule:
- django: about 5,600 `Model.objects.X(...)` calls tie between
`QuerySet.X` and same-named methods elsewhere. `Library.filter`,
`EngineHandler.all` and `HttpResponseBase.get` are each one segment
shallower, so the QuerySet share fell from 57 % to 24 %.
- Kotlin / Java: a call went to a property or field that merely shares
the callee's name. Kotlin `where(...)` went to `var where`, and Java
`esr.indexMode()` to a field whose QN lacks its class segment.
Kotlin CALLS edges on Variable/Field targets went 785 -> 1,648.
- C#: .NET reference assemblies (`src/libraries/<Asm>/ref/<Asm>.cs`,
`throw null` bodies) are shallower than the implementation, and "ref"
sorts before "src". 14.1 % of dotnet/runtime's CALLS landed on such
stubs (4.9 % in v0.11.0).
A tie now goes to:
1. the more plainly callable candidate: Function/Method, then any other
definition, then an unknown label, then Variable/Field;
2. for C# only, an implementation over a reference-assembly stub;
3. the lexicographically smaller QN.
This is still a pure function of the candidate set (O9). The kernel
cases behind 1a342553 keep their answers: `dev_name` is the only Function
among the fields, and include/linux sorts before tools/virtio.
Measured on the bench corpora (macOS arm64, main cb8b641f vs this
change, one cold full index each):
- django: `objects.X` -> QuerySet 23.7 % -> 41.4 %; CALLS -> Variable
1,022 -> 741
- Kotlin (Exposed): CALLS -> Variable 1,648 -> 480
- Java (elasticsearch): CALLS -> Variable/Field 62,313 -> 42,957
- C# (dotnet/runtime): CALLS -> ref/ stubs 143,799 -> 52,000;
CALLS -> Variable/Field 33,779 -> 28,662
- TypeScript: CALLS -> Variable 7,978 -> 7,341
The rest of the django gap needs Manager -> QuerySet typing, which no
tie order can supply.
Tests:
- registry_tie_prefers_callable_over_property
- registry_tie_ignores_nesting_depth
- registry_tie_prefers_csharp_implementation_over_ref_stub
All three are RED on main and RED again with the registry change
reverted.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
CI's cppcheck 2.20 failed the lint job on rr_c_role: after the last dot was
cut to '\0', a second strrchr looked for the owner's dot, and cppcheck took
that result for the first one ("Condition 'prev' is always true",
knownConditionTrueFalse). The owner is now the segment found by walking back
from the last dot to the dot before it or to the start of the name: the same
owner, without a second search (Homebrew's 2.21 did not report it).
rst_c_member_owner covers the member path, which no test reached: the owner
running back to the start (point.x), to the dot before it (shapes.box.x),
through "struct" and -> (struct point->y), and a field taken for a struct
(box.corner.y, missing). It passes on the old and the new code alike, and
fails when the walk ignores the dot before the owner.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Every process start fingerprints its own executable for the daemon build
identity, and the hook path pays for that before it can answer. With the
portable transform that was ~1.2 s of CPU for the ~300 MB binary, so on a
slower machine hook-augment hit its 2000 ms deadline and printed nothing
(#2441).
Two changes in src/foundation/sha256.c:
- cbm_sha256_update compresses whole blocks straight from the input instead
of copying every byte through the context buffer.
- Block compression runs on the CPU's SHA-256 instructions when it has them:
ARMv8 SHA2 (sysctl on macOS, HWCAP on Linux, IsProcessorFeaturePresent on
Windows) or x86 SHA-NI (CPUID). The backend is probed once; the portable
transform stays the fallback.
Measured on an Apple arm64 Mac (macOS 26.6), release builds of main 72a2c0bc
and of this change, hook-augment SessionStart in fresh isolated dirs, 9
interleaved runs, medians:
- hook-augment: wall 1.448 s -> 0.376 s, user CPU 1.162 s -> 0.097 s
- SHA-256 of the 303 MB binary: 1.18 s -> 0.095 s CPU per pass (the
block-direct update alone, portable rounds: 0.74 s)
- with CBM_HOOK_DEADLINE_MS=1000 (a slower machine): before, exit 0 with
empty stdout after 1.01 s; after, the full 188-byte answer in 0.37 s
Digests are unchanged: the build id the new binary logs equals the file's
SHA-256, and the hook output is byte-identical. A new test compares the
hardware and portable backends at every length 0..299 and at block-boundary
sizes up to 1 MiB under eight update chunkings, plus the NIST million-'a'
vector; it fails if the ARM schedule is broken or if Apple arm64 falls back
to portable. The x86 path was checked under qemu-x86_64 -cpu max (2,436
comparisons equal, million-'a' correct) and falls back to portable on a CPU
without SHA-NI. On a Windows arm64 VM the probe picks the hardware path:
0.094 s against 0.82 s portable for the 303 MB binary, digest equal to
sha256sum's.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolves the definition walks next to each other: main's decl_namespace (#2120) and this branch's extra_props are both compacted and both counted in the result census.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolves the index_status tool description: main's watch state (#2167) and this branch's doc_links both stay.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Doc -> code candidates now carry a p judged for their kind, their document
format and where the document sits.
Format (sample 6, 234 blind-judged candidates): reStructuredText, AsciiDoc and
PDF sections each get their own curve (reST stored from tfidf 0.30, AsciiDoc
from 0.20, PDF not measured and not stored). A candidate that repeats an
exact MENTIONS link of the same section is not stored.
Position (sample 7, 490 blind-judged candidates from four monorepos): every
document gets a home folder -- its folder; a docs folder documents its
parent; a folder without code hands over to the nearest one with code; the
repository root means the whole project. Markdown candidates outside the
document's home were right 6 of 90 and are never stored; inside it they are
stored from tfidf 0.30 (0.33 / 0.53); for documents of the whole project
samples 4 and 7 pooled give 0.22 / 0.51 from 0.30.
New kinds, all in doc_link_candidates (evidence gains "kind" and
"position"; no new edge type, no schema change):
- local: up to two home functions ranked below the top five (0.32);
- file: a whole file, its vector the sum of its functions' terms and its
definitions' names, config keys included, cut to 64 terms (0.30, from
tfidf 0.40);
- folder: a README or index document -> its home folder (0.87).
get_code_snippet on a section lists every kind with its label;
get_file_outline shows the sections about the file or a folder holding it
(cbm_store_doc_candidates_for_path).
cbm_sem_p_2dp rounded 0.325 and 0.525 down (their float values sit 1.2e-6
below the half); its epsilon is 1e-4 on the x100 scale.
Replayed on the sample repositories, the build stores exactly the rows the
judged curves select (950 rows, 0 differences).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The candidate rows and their strings were allocated with calloc/strdup
in the semantic pass and in cbm_store_doc_candidates_get, and released
with free. They now go through the memory core in CBM_MEM_CLASS_STORE
(cbm_calloc, cbm_mem_strdup, cbm_free) on both sides, so the rows the
pass hands to the pipeline and the rows a read returns are accounted
like every other store buffer. make lint-memory-core: no file grew.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A documentation section that explains a function rarely names it, so
the deterministic doc links cannot connect them. The semantic pass now
ranks, for every Markdown section that can be about code, the functions
whose TF-IDF terms best match the section's heading and body, over the
functions' own corpus (function-pair scores are untouched):
- gate: no candidates for release-number or project-meta headings
(changelog, licence, contributing, links, contents, install, ...),
bodies under 20 words, or bodies that are mostly links;
- targets: functions and methods, never tests (by path or name);
- stored: a section's best five with tfidf >= 0.20, each with p, the
probability that the section is about the function, from 274
blind-judged candidates in five held-out repositories (three reading
rounds): 0.36 below 0.30, 0.42 below 0.40, else 0.55. Below 0.20 the
judged share was 0.06, so nothing is stored there.
These are candidates for an agent to verify, not graph edges: at best
about half are right, far below the bar for a link. They live in a side
table, doc_link_candidates (section and function qualified names, rank,
score, p, evidence), created when a generation is published, so older
indexes read as "no candidates". Full and legacy-partial reindexes
replace a project's rows; the delta route keeps the previous rows (its
proxy buffer has no section text or function tokens to recompute
them), and a row whose node is gone is skipped when read.
get_code_snippet shows them: on a Section, "possibly_about" (its
candidate functions); on a function or method, "possibly_described_in"
(the sections), at most five, highest p then score first, with a
candidates_note saying what they are.
Measured on fastapi (6,144 gated sections): 24,293 rows, identical to
the generator's dump; index time within noise; the database grows by
the table (37 MB there, with a 148-character project prefix on every
qualified name).
Tests: semantic:sem_doc_calibrated_p_bands,
store_arch:doc_candidates_replace_and_get (RED when a replace keeps old
rows), pipeline:pipeline_doc_candidates_published_and_kept_by_delta
(RED with test functions admitted, the heading gate off, the publish
write skipped, or the delta route replacing rows),
mcp:snippet_doc_candidates_both_directions (RED with stale rows shown).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The semantic pass read every string property of a function (body text,
signature, return type, decorators, docstring) with a reader that
stopped at the first '"' and kept escapes raw: a value holding an
escaped quote lost everything after it, and "\nreturn" became the token
"nreturn". cbm_sem_json_str honours the escapes: \" \\ \/ keep their
character, \n \t \r \b \f and \uXXXX become a separator.
The tokens change, so admissions change: on 13 repositories 1,340 ->
1,387 admitted SEMANTICALLY_RELATED pairs, 614 of them unchanged. Of
the dropped pairs that earlier samples had judged, 52 of 89 were
related (0.58); of the kept ones, 73 of 92 (0.79). A pre-registered
blind sample of 100 newly admitted pairs from 11 held-out repositories
(three reading rounds) judged 65 related (0.65, Wilson 0.55-0.74), so
the admitted set is 0.72 precise against 0.69 before. The same index
run twice gives identical pairs and scores.
"p" is refit on the new admitted population (bands at the quartiles of
the admitted scores, pooled where a higher band judged lower; added and
kept pairs weighted by their share): score < 0.773: 0.63, else 0.76
(was 0.56 / 0.64 / 0.73 / 0.86 in four bands).
Tests: sem_json_str_honours_escapes (RED with escapes ignored),
sem_calibrated_p_bands.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
When a function has more candidate partners than its budget of ten
SEMANTICALLY_RELATED edges, admission took the pairs in discovery order:
the first ten won, whatever their scores. Admission is now best-first
(cbm_sem_admit_best_first: descending score, ties in the canonical
order, still a pure function of the sorted inputs). On a blind judged
sample from held-out repositories, the pairs first-come admitted over
budget were 0 of 17 related; the pairs best-first admits instead, 7 of
20. Where no budget binds nothing changes (django 66 edges identical;
fastapi 426 -> 430 edges, 222 changed).
Every SEMANTICALLY_RELATED edge now carries "p": the probability that a
reader judges the pair related, by score band, from 120 blind-judged
admitted pairs (score < 0.778: 0.56, < 0.806: 0.64, < 0.852: 0.73,
else 0.86). p is computed from the score as stored (three decimals),
so one shown score always has one p. Scores and the 0.75 threshold are
unchanged; a scoring change (TF-IDF alone, or weights fitted on the
first sample) was judged on the same sample and did not beat the
shipped score by the pre-registered margin.
Tests: sem_admit_best_first_keeps_the_best, sem_calibrated_p_bands,
pipeline_semantic_edges_carry_p; each is RED with its behavior
reverted.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The TF-IDF signal keyed each term by the token's POSITION in the
function, so any two functions of similar length shared their first
terms whatever their words: 0.20 of every SEMANTICALLY_RELATED score
measured length, not vocabulary. Terms are now keyed by corpus token
(cbm_sem_tfidf_terms: one term per distinct word, weight term
frequency x idf, ascending token index, which the sparse cosine
merges on).
Measured on a blind judged sample: 222 function pairs from six
held-out repositories, stratified by score band, read in three rounds.
The share of admitted SEMANTICALLY_RELATED edges a reader judges
related rises from 0.713 to 0.742, with about 19% fewer edges (fastapi
507 -> 426, django 64 -> 66). The same sample shows that no score
threshold reaches 0.90 precision, so the documented "~95% at 0.75" did
not hold before this change either.
The harness that measured it ships with the fix, off by default:
CBM_SEM_PAIR_SIGNALS=<path> writes one line per scored pair (both
qualified names, the score, every signal value, whether the pair was
admitted, both locations); CBM_SEM_PAIR_SIGNALS_FLOOR also records
pairs below the threshold, never admitting them. cbm_sem_combined_score
is now cbm_sem_signal_values + cbm_sem_combine (same arithmetic, same
order). CBM_SEMANTIC_INDEX_VERSION 3 -> 4: indexes built before score
pairs differently.
Tests: sem_tfidf_terms_compare_vocabulary_not_positions,
sem_combine_weighs_signals,
pipeline_semantic_pair_signals_never_change_the_graph; each is RED with
its behavior reverted.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A second held-out census of AsciiDoc code names (61 of 83) failed on
one class: 21 of its 22 errors bound a Java package folder to a dotted
name that is no code at all. Debezium's `snapshot.mode` and
`debezium.sink.type` are configuration keys, OpenNMS's
`org.opennms.features.amqp.eventreceiver` is an OSGi configuration id,
`org.apache.cassandra.auth` there is a JMX domain, `sk.ainet.vendors` a
Maven groupId; each spells the path of a real package directory.
A code name now never binds a folder whose first code definition is in
a Java, Kotlin, Scala or Groovy file. A Python package is what its
dotted name says and keeps binding. Like the four rules before it, this
one only takes links away.
Proof: development corpora: fastapi, dask and django unchanged;
junit-framework loses 2 links to package folders (the docs name
`org.junit.jupiter.api.condition` as a package, so those two were
right: a recall cost of the rule). Judged held-out replays: Markdown
and reST code names keep all 195 links judged correct and lose none;
the failed AsciiDoc census loses its 21 judged-false folder links and
its 3 judged-correct ones. The AsciiDoc family stays off until a new
census.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
AsciiDoc code names (resolved like Markdown's) failed their held-out
census at 43 of 80. Four of its error classes have a rule that holds
for every format:
- a name written from the root `std` never binds a C/C++ definition or
header of the repository: `std::complex` is the standard library's,
not Boost.Typeof's `typeof/std/complex.hpp`;
- a name written from a JVM root package (`com`, `org`, `java`, ...)
binds a JVM definition only where it starts the package (after a
source-root piece such as `src/main/java`), never as the tail of a
relocated copy (`...hbase.shaded.com.google.protobuf.Message`);
- a capitalized type of a Java, Kotlin, Scala, Groovy or C# definition
matches in its own case only: `debug.log` is a log file, not the
method `log` of class `Debug`;
- a definition in another copy of the document's project never answers
the document: below the directory where the two paths part, the
document's own branch holds the same rest of the path, at least two
directories deep, as a file with definitions (snapshots side by side;
a shared `__init__.py` is no copy).
Each rule only takes a link away: a refused candidate still counts, so
two candidates stay ambiguous, and it is never the one linked.
Proof: six development corpora (junit-framework, fastapi, dask,
pydantic, curl, django) keep all 1,730 code_name links. Replayed on the
judged held-out samples: Markdown and reST code names keep all 195 links
judged correct and add none; on the failed AsciiDoc census the rules
remove judged-false links only (7 in the vendored-Boost repository,
which loses 127 code_name links and gains none). The first version of the copy rule dropped
one (a package's `__init__.py` taken for a copy) and turned ambiguity
between copies into new links; both are fixed here. The AsciiDoc family
stays off until a new census.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A Java class may declare a field and a method of one name (`executable`
and `executable(Path)`), and the graph keeps one node per qualified name,
so one of the two was lost: the field in 90 and the method in 17 of the
107 such pairs in junit-framework. With fields named correctly (the
previous commit) every initialized field joined these collisions.
When the same class body declares a method of that name, the field now
takes the qualified name `<Owner>.<name>#field`, the `base#suffix`
fence of `#macro` and the Rust cfg twins; every other field keeps its
name. The registry already files a fenced symbol under its plain name,
so name lookups still reach it. A read, a write or a plain value
reference that resolves to such a method goes to its field twin instead
(cbm_registry_value_target); calls and callable references keep the
method.
junit-framework (development corpus), before -> after: Field 2,808 ->
2,898, Method 14,124 -> 14,141 (the 107 pairs, both nodes kept); WRITES
into these methods 66 -> 0 and into their fields 1 -> 96; USAGE into
these methods 97 -> 0 and into their fields 2 -> 212 (accessors reading
their own field were self-edges before); CALLS into the methods 1,158 +
24 (then landing on the field) -> 1,215.
A C# partial class split across files can still collide: the check sees
one class body.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Supplementary held-out audits, each registered before its queries and run on
repositories chosen by an objective code-search walk, disjoint from every
earlier audit and development corpus (blind readers, a second reader on
every link not judged correct and on a seeded tenth of the rest; bar: Wilson
95 % lower bound >= 0.90 and point >= 0.95). A fix is judged on a NEW sample,
never on the one that found the error.
Ship now:
- Markdown code_path, refined to code spans with two or more path segments:
97 of 100 (lower bound 0.916). A file or one directory named alone in a
code span is its own family, bare_path, held: across the judged samples it
was 28 of 41 correct (`config.yaml` the reader's own file, `doc/` another
project's directory). A span without a word character (`../`, `./`) is no
path at all.
- reST literalinclude: 100 of 100; an open range (`:lines: 5-`, `-5`) now
reads the included file for its end and binds like a closed one.
- reST code_path: 99 of 100; its one error, a bare `../`, is fixed above.
- PDF qualified names: 97 of 99 after the owner rule: a definition whose
qualified name lacks its owner (a Go method under its package) matches
only owner-qualified text, so `time.Now` no longer binds a receiver's Now.
Fixed, still held:
- reST object directives: a function or class directive never binds a
Module, Folder or File (an example script's module took them); a new
sample is due.
- ADR supersedes: three censuses failed (37/38, 144/163, 82/87). Fixed from
them: a combined template label (`Supersedes / Depends on:`), a value of
"none" or "n/a" followed by records it extends, another record as the
subject (`ADR 0239 supersedes ...`, a table cell `0259 (supersedes ...)`),
a full stop after a number ending the target sentence while a
`[text](destination)` link stays one reference (adr-tools'
`Supersedes [3. Title](0003-...md)`). The family is split: a statement that
opens its line or field and names a record directly stays `supersedes`
(44 of 45 on the last census); the same words in running prose are
`supersedes_prose` (38 of 42), held. Both still held in this commit; a
fresh census of statements passed afterwards and is acted on next.
- An ADR log in a repository without code definitions lost every supersedes
link by file: the ADR table was filled after an early return. It is now
filled first.
Every fix has a test that fails with the fix reverted.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Markdown sections linked to code, ADR nodes, reStructuredText and
AsciiDoc sections, PDF pages and the files newly discovered for them
change the graph of documents that did not change. An index written
before (version 5, the doc-comment layer) keeps those documents as they
were, so it is rebuilt in full once.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
AsciiDoc (.adoc, .asciidoc) is now indexed, without a grammar like PDF: a
Section per heading (`=` .. `======`, or `#`), the text up to the next
heading as its docstring, delimited blocks respected (listing, literal,
comment, passthrough, example, sidebar, quote, table, fences). Its
references become MENTIONS edges, via "asciidoc", from the section they are
written in:
include include::target[...] -- Asciidoctor's preprocessor directive,
read in every block but a comment block; the page's own
attributes, then the Antora component's and the playbook's,
substituted
attribute an {attribute} whose value is an Antora javadoc link
({javadoc-root}/<module>/<package path>/<Type>.html[#member])
binds the Java or Kotlin type, or its one member, that the
repository's main sources declare under that path
code_path, monospace spans naming a path or a qualified name (the
code_name Markdown code-span rules)
The rules are the field test's (H8 Asciidoctor/Antora harvester, audited
~100 % precise): an Antora resource ID ([component:][module:]family$path)
names <component>/modules/<module>/<family dir>/<path>, or the file a
collector scan contributes there (a scan of build output is generated: no
row); any other target is relative to the including file. `lines=` and
`tag=`/`tags=` regions bind by H8's rule: edge blank and comment lines
trimmed, the innermost enclosing definition, unless that is a type (or there
is none) and the definitions inside cover every other line (an annotation
right above one counts) -- then that definition; tag regions come from the
included file's own `tag::x[]` / `end::x[]` comments, read from that indexed
file; a tag the file lacks binds the file. One reference makes one edge: a
region holding several top-level definitions binds the file.
Antora settings travel as YAML scope blobs (antora.yml: component name,
asciidoc attributes, collector scans; antora-playbook*.yml: attributes),
read as text, so an incremental run resolves as a full one; a change to them
is GLOBAL. AsciiDoc joins the documents bound by lines: a body edit of an
included file re-resolves its pages.
Also: the Markdown resolver's module index no longer takes AsciiDoc or PDF
files for code modules.
Proof on junit-team/junit-framework at 6c8ca116 (H8's corpus and commit),
C against H8's recorded links: javadoc attributes 362/362 identical;
includes 292 identical, 60 bind the type enclosing the several definitions
H8 names, 7 regions with several top-level definitions bind the file. 317
sections, 668 edges, no include rows (9 generated partials, 1 file type the
indexer does not read); monospace spans 30/30 correct by hand. 8.3 s for the
whole index.
Tests: suite doc_links_adoc (structure and tokens, the Antora blob, the
pipeline: resource IDs through a collector scan, tag regions, a region with
its imports binding its class, lines=, javadoc attributes, spans; incremental
== full: a moved tag region, a changed component attribute). Revert check:
without H8's region rule and without AsciiDoc among the line-bound documents
exactly their assertions fail (observed).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The closure route treats a body-only edit (the file's surface, which holds
no line numbers, unchanged) as "the file re-resolves, nobody else does".
That holds for references that bind names, but a document can bind lines:
Markdown `app/routing.py#L8-L9` and reST `.. literalinclude:: :lines:` bind
the innermost definition holding those lines. A body edit that moves code
left such a document on its old target on an incremental run, while a full
index bound the new one (observed: the doc_links_md and doc_links_rst
"lines moved" steps, incremental != full).
The documents (Markdown, reST: cbm_doclinks_binds_lines) with edges into a
body-edited file now join the repair closure, from one more dependent-files
query over the body-edited files. Code files stay out: their references
bind names, which a body edit keeps. The query shares the existing
dependents query's block and failure path (no new allocation site).
Tests: a "lines moved" step in doc_links_md_incremental and in
rst_links_incremental; with the predicate disabled exactly these two steps
fail (observed). doc_links_md/rst/pdf, doc_mentions, incremental, pipeline
pass.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A reST document now gets a Section per title (the text up to the next title
as its docstring, its lines from the title to the next one) and MENTIONS
edges, via "rst", from each section to the code it names:
role :class:`pkg.Model`, :meth:`~x.Y.z`, :func:`.f`, :c:func:`f`,
and extlink roles whose URL is a repository blob path
(:source:`django/db/models/base.py`)
object .. py:class:: / .. method:: / .. function:: ... and their
C-domain twins
autodoc .. automodule:: / .. autoclass:: ...
include, .. include:: / .. kernel-include:: of a code file,
literalinclude .. literalinclude:: (its :lines: bind the innermost
definition holding them, kept as target_lines; its
:pyobject: the named definition)
kernel_doc .. kernel-doc:: path and its :identifiers: / :functions:
code_path, inline literals naming a path or a qualified name (the
code_name Markdown code-span rules)
Structure is the field test's line model (H3): titles by adornment style,
literal blocks after `::` and in code directives, comments, directive
arguments and options, Sphinx's module / currentmodule / class contexts,
CPython's str whitespace and \w. Text before the first title belongs to the
file. A Sphinx documentation set's conf.py is read as text, never run: its
source_suffix makes discovery index e.g. Django's `.txt` documents as reST in
that set only, its primary_domain decides undomained roles, its extlinks and
intersphinx names are known to the resolver.
Python names resolve the way Sphinx imports them: the longest prefix that is
a module's import name (named from its topmost package directory), the rest
by qualified name, a package's re-exports (`from .base import Model` in its
__init__.py) followed. The re-exports and the conf.py settings travel as
Python scope blobs (doclink_py.c), so an incremental run resolves as a full
one; one resolver serves reST and Python. Forms in Sphinx's order (T, C.T,
M.T, M.C.T; a leading `.` reverses it). EXACT when a form names a node,
otherwise UNIQUE by name with the field test's fixes: a bare member
(:meth:`save`, :attr:`x`) is never searched (37 of 40 such results were
wrong), an object directive's target lies in its declared module, a module
never answers a member's role (`:meth:`dispatch`` is not tests/dispatch),
test code is no target of a search but binds when the document writes its
name in full (`django.test.TestCase`). C identifiers are whole names: the one
C/C++ definition of that name and kind. Unresolved: missing when the name or
path is the repository's, ambiguous, test_only_target; another project's
name (stdlib, intersphinx) is no row.
reST architecture decision records: the ADR detector reads reST records
like Markdown ones (titles underlined with = or -): course-discovery's 34
decision records become ADR nodes.
Proof on Django's documentation (the field test's corpus and commit), C
against the frozen H3 harvester over the same graph, per (document,
section, target): 6,695 of H3's 7,238 links identical; H3-only 543 = links
C attributes to the document because a section title repeats in it (the
Markdown rule; the same document -> code link exists), deliberate refusals
(bare members, directive searches without a module) and H3's own audited
error class (router protocol methods -> ConnectionRouter, forms Field ->
model Field); C-only = the same attribution mirrored, plus 1,053 inline
literal links (sample of 30: 30 correct). 6,574 reST sections; 12 s for the
whole index.
Tests: suite doc_links_rst (structure and tokens, the scope blobs, the
pipeline's rules, source_suffix in discovery, reST ADRs, incremental ==
full). Revert check: without the module-kind guard and without the
source_suffix mapping exactly their assertions fail (observed). Two
expectations of the old behaviour now name the new one: the reST grammar
golden and probe have the document's Sections (they recorded "reST titles
are no Sections", the gap this closes).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A PDF is now indexed: its text layer is read by a dependency-free extractor
(internal/cbm/pdf, zlib only) and every page becomes a Section node ("page N",
the page's text as its docstring, property page), so search finds what a page
says and get_code_snippet shows a page's text, never the file's bytes. The code
a page names structurally becomes MENTIONS edges from that page, via "pdf":
pdf_path a repository path ("pkg/server/handler.go", "./x/y")
pdf_file a file name with a code extension ("models.py", "Pool.sol")
pdf_qn a qualified name ("app.models.User.save", "a::b", "x->y")
Bare identifiers never become edges (field test E6c: a type-target rule failed
its pre-registered bar), and only EXACT or UNIQUE resolutions link: the field
test's tiers (path-exact, component-aligned path-suffix, qn-exact, qn-suffix,
the latter also through a node's owner -- a Go method's QN has no receiver --
and with the same one-entity collapses). A mention split across a line break is
proposed as one reference and replaces its fragments only when it resolves.
Hygiene as measured: no links from PDFs under test or fixture directories (R1),
none to data files (R2), none for keyword arguments (R3); a test, mock or
fixture target is a test_only_target row (R4, with the test directories the
field test found missing). A path or name whose first component is not of this
repository is no row at all.
The extractor is a port of the field-tested prototype: xref tables and
streams, /Prev chains, repair near a wrong offset and reconstruction from the
object headers, object streams, Flate (with partial recovery), LZW, ASCII85,
ASCIIHex, RunLength and PNG/TIFF predictors, simple fonts (base encodings,
/Differences, built-in Type1 encodings, standard-14 widths, Type3), ToUnicode
and encoding CMaps, Type0/CID fonts with the embedded TrueType cmap read
backwards, Form XObjects, inline images skipped, word and line breaks from
glyph geometry, right-to-left runs reversed. Its geometry is bit-exact on every
platform (no FMA contraction; CPython's hypot). Untrusted input: every read is
bounds-checked, recursion is depth-limited, integers from the file saturate, a
decoded stream is cut at 512 MiB, and every lookup that could rescan the file
per object or per glyph is answered from an index built once. Not read:
encrypted documents (reported as such); scanned pages have no text layer.
The mention scanner is the field test's (idents.py) with CPython's semantics:
NFKC per code point plus canonical composition and the str predicates, from
generated Unicode 16.0 tables (pdf_unicode.c), and re's leftmost-first
matching.
Pipeline: CBM_LANG_PDF (no grammar; the registry ledger gets a DOCUMENT
capability, the call-node manifest exempts it like the other grammar-free
classification), extraction before any grammar lookup, a resolver hook
file_prepare (a file's state from all its references before any resolves).
Incremental: a proxy node carries parent_class where owner + name differs
from its QN (Go methods, class-body variables), so a re-resolved document binds
a Go method as a full build does.
Proof against the field-test prototype on its corpus (749 PDFs from 21
repositories, restored at the recorded commits, sha256-verified): text
byte-identical on all 1,492 pages of the 720 comparable PDFs (0.73 s vs 14.7 s);
mention tokens identical on all 960 page-text sets of E6, E6b and E6c (26,435
tokens); links identical to the frozen Python pipeline run over the same graph
on semantic-kernel 5/5, cosmos-sdk 30/30, Polly 11/11, mediastore3 3/3, dask
4/4. 44,070 seeded mutants (truncations, flips, nesting and keyword bombs, on
the originals and on uncompressed copies) under ASan and UBSan: 0 findings
after the first campaign's integer overflows were fixed as a class.
Tests: suite doc_links_pdf (layout, fonts and CMaps, object and xref streams,
xref repair and reconstruction, Form XObjects incl. a self-referencing one,
inline images, filters, hostile input, the scanner against the field test's
output, the pipeline's tiers, hygiene and rows, back navigation, the snippet,
incremental == full).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A Markdown file that is an architecture decision record now also defines an
ADR node (File DEFINES ADR): its name is the record's canonical id ("ADR-12";
another prefix as written, "DEC-5"; a dated record keeps its file stem), its
qualified name "<module>.__adr__", and its properties carry the record's
facts: adr_id, title, status (normalized: proposed, accepted, implemented,
superseded, deprecated, rejected, abandoned, draft, unknown), status_text,
date, deciders, superseded_by, and the decision itself as the docstring, so
search finds a record by what it decided. Its sections link to code like any
document's (MENTIONS, previous commit).
Detection is per file, with the field tests' rules: a file in a conventional
ADR directory (adr, adrs, decisions, decision-records, ...) whose name carries
an id or whose text has ADR structure; any adr-NNN file; a numbered or dated
file with ADR structure. Never README, index or template files without an id,
never test fixtures. Facts come from front matter, then header fields
(bullets, bold, tables), then the Status section; the date also from a
"Last updated" field, the change log's first date and the file name.
An ADR's own statement that it supersedes another ("Supersedes [ADR-3](...)",
"Supersedes ADR-3"; "Replaces ADR-3" where it opens a line, never "replaces"
as a verb of prose) becomes a SUPERSEDES edge between the two ADR nodes, by
link or by id. "Superseded by" is written in the old record about another
file; an edge from the other node would be owned by a file whose text does
not hold it, so it stays a fact of the old record (status, superseded_by).
The link layer gains a per-family edge type (doclink.h: the family list's
"edge" column), so a family can produce SUPERSEDES instead of MENTIONS; edges
are grouped by (type, source, target). Definitions gain a tail field for a
document's facts (extra_props: key, value pairs), carried through result
compaction and written by both property builders.
Measured against the field-test harvester on five repositories: detection
182 of 182 records, the same files; title 182/182; status 179/182 (cosmos
ADR-050 and its annexes say "Status: ARCHIVED", kept as status_text with
status unknown); date 174/182 (semantic-kernel's "date: {2024-05-02}" is read
as a date where the harvester dropped it). SUPERSEDES: semantic-kernel
0038 -> 0015, log4brains 20201016 -> 20200926, each checked against its
sentence. Markdown MENTIONS unchanged on fastapi, curl, dapr, cosmos-sdk.
Not covered: ADRs in reStructuredText, and ADR-log configuration files
(.adr-dir, log4brains) as a detection source.
Tests: doc_links_md_adr (detection and exclusions, facts from front matter
and from a Status section, SUPERSEDES by link and by id, no edge from prose,
the record's MENTIONS, incremental == full for a status edit and for an
added record named by id).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A Markdown document is documentation in itself: its sections now link to
the code they reference explicitly, as MENTIONS edges (via "markdown") from
the Section node (the File node for text before the first heading, and for a
heading whose title repeats in the file, whose Section node it does not
own). An agent can go from a document to the code and back through the
graph: trace_path inbound with edge_types ["MENTIONS"] lists the sections
that name a definition.
Families (the "syntax" property; each with its ship gate):
- link: the destination of an inline link or a link reference definition
(no URL, no in-page anchor, no image);
- path: a bare path in prose (a slash and a file extension);
- code_path: a code span or HTML <code> span holding a path, a file name or
a directory, with an optional line range (#L3-L9, :3-9) or member
(path::Name);
- code_name: a code span holding a qualified name (a.b, a::b, A#b, A\B,
mod:attr).
Resolution is guess-free, with the rules the documentation field tests
measured:
- a path is read relative to the document and, when it has a directory
part, to the repository root; a file name alone only next to the document;
- a line range binds the innermost code definition holding the whole range,
else the file with "target_lines" on the edge; path::member the one
definition of that name in the file;
- a qualified name binds the one code definition whose qualifier chain ends
with the written qualifier, else the one module or package so named; a
match as written outranks one that needs case folding; a field binds only
through its type, never through an instance; module:attr only in Python;
product documentation never binds test code;
- a bare name is never an edge.
Unresolved references are rows in doc_link_unresolved: missing (the
directory or the repository's own root is there, the name is not),
ambiguous, test_only_target. A URL, an image, a URL route (/docs), a file of
a type the indexer does not read, and code of other projects are no
references to the repository and leave no row.
Extraction scans the document's bytes once (doclink_md.c); every scan of a
line is linear, or n log n where it sorts, using position lists that grow
with the bytes they track, not with the line. Front matter, fenced code,
HTML comments and MDX import/export lines are not scanned.
The incremental route's node proxies now carry their line numbers
(pipeline_delta.c), so a re-resolved document binds a line range exactly as
a full build does; the Markdown resolver reads definitions from its own
by-file list of the run's nodes, never from DEFINES edges, which an
incremental run holds for re-extracted files only.
Measured on four repositories against the main branch (which links file to
file only, REFERENCES_FILE): fastapi 2,899 MENTIONS edges (2,225
REFERENCES_FILE before), cosmos-sdk 1,286 with 205 distinct definitions
reached and 188 edges from 41 ADRs into code, dapr 184, curl 52.
Link pairs agree with the field-test harvester (cosmos-sdk 408 of 409; the
difference is a link of a document to itself, skipped on purpose). A hand
check of 45 sampled qualified-name links found one wrong, fixed by the
exact-case rule above.
Tests: doc_links_md (extraction, repeated headings, the span classifier
table, resolution with every reason, back navigation, incremental == full
for a code edit, a document edit and an added name).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Conflict in internal/cbm/result_compact.c: main (#2543) added the variants copy and this branch the decl_namespace copy at the same place in cr_walk_def; both kept.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The doc_links report added two early returns to handle_index_status, each
with its own free of the project name: raw allocator sites the memory-core
ratchet does not allow (mcp.c went from 778 to 779 on this base). A failed
allocation or report now sets a flag and leaves through the existing exit,
which frees the name and answers with the MCP allocation-error response as
before. The baseline of mcp.c is lowered to the 777 the ratchet now
measures.
Tests: doc_mentions (incl. the doc-link report fault injection), mcp pass;
lint-memory-core passes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The first run of the doc-link suites on the Linux arm64 leg (gcc, ASan with
LeakSanitizer) found three things macOS does not show:
- The node pass bound files in the order the file system listed them, and
the scratch table's cost depends on that order (the table is emptied per
file and made anew only after a far larger file). doc_mentions_cs_scratch_table_work
counted 102,912 steps on Linux against 26,048 on macOS for the same
files. Files are now bound in path order: a file's bindings do not
depend on the others, so only the cost changes, and it is the same on
every system. The test itself had a second dependence: its 20,000-field
file exceeded the extraction budget on that leg (5 s, defs=0), so the
test ran without it; 4,000 fields were still cut there beside five other
suites. It now uses 1,000 fields and 20 small files (3,008 steps against
a bound of 4,160; 40,512 with the S13 fix reverted) and requires the
large file to reach the index whole.
- A test thread that extracted C# on its own small stack ended without
freeing its parser and node-kind caches, as worker threads do: 40 KB
reported leaked at exit. It frees them now.
- The suite ran 1,014 s against the harness's 900 s per-suite wall clock
(gcc on aarch64 makes ASan's stack-use-after-return check about 100x
slower per call). Its 119 tests now run as doc_mentions (72),
doc_mentions_cost (9, the work-counter tests) and doc_mentions_msbuild
(38): about 230 s, 210 s and 480 s there.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The unit memo of the previous commit was asked -- the run's lock taken, an
entry made -- for every unit step, also when the unit has no directive or
its index rules the name out, where the walk returned at once before.
That is lock traffic and memory for most projects, which have no global
usings.
The test that walk starts with is now one function (usings_may_give),
asked before the memo too. The work counts of the doc_mentions cost tests
are those from before the memo again, except where unit directives can
give the name: one memo probe per lookup there.
Test: cs_unit_usings_across_files_work and cs_unit_usings_unchanged as
before (768 -> 1520 steps; without the memo 17003 -> 64403).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The using step of a lookup is remembered per file (S5), so the unit's
directives -- its global usings and <Using> items, asked at the global
namespace -- were walked again by every file that asks a name: files x
min(directives, scopes declaring the name), a hang in resolution for a
project with many global usings and a common name.
What a unit directive gives a query reads only the context's unit,
assembly and product flag (level_using and what it calls), never the file.
The run now keeps, per (unit, assembly, product/test, query), the unit
directives that give the query anything at all, in order, up to where they
alone decide it (cs_unit_memo). Each file asks those after its own
directives and stops where the walk of all of them stops. The edges and
rows are unchanged: a directive that leaves an empty step empty makes no
change in any step, and a step that starts with the file's candidates is
decided no later than the unit's directives alone are (a candidate counts
unless it is the step's first).
The memo is made once the index is complete (the build's own lookups see
directives that are not all resolved yet) and is shared by the resolve
workers: each list is found exactly once, by the first worker that asks;
the others that ask for that key take its lock until the list is there, so
the work of a run does not depend on the scheduling. Out of memory, it
stops remembering and the walk is the full one.
A test seam builds the index without the memo.
Tests: cs_unit_usings_across_files_work (k global usings, k scopes
declaring the name, f files naming it once: k 150 -> 300 and f 100 -> 200
give 768 -> 1520 steps, 1.98 x; without the memo 17003 -> 64403, 3.79 x);
cs_unit_usings_unchanged (the same edges and rows with and without the
memo, over the cases of file and unit candidates: none, other, same, same
then other, test-only, another unit).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A scope written in this run that the reader refuses costs its own file:
what its parse set is taken back and the file declares nothing. Other
files then found none of its declarations either: a reference to one of
its types was `missing`, or bound uniquely to another type of that name
which the full picture would not choose (a type of the enclosing namespace
comes before an imported one). Unplaced declarations are quarantined for
exactly this reason; a refused file's were not.
Now the type names the refused blob's T and Q records carry, read
leniently (a line counts when its name field is there and is a name,
however the rest of it reads), are quarantined, so a reference to one of
them from any file is a graph gap. The stored marker carries the same
names as Q records after its `!` record, so a later run that reads the
marker back quarantines them too and an incremental run equals a full one;
cs_scope_marked_rejected takes the marker followed only by Q records of
names. The resolver's rejected_scope is therefore a function of the
refused blob, and cbm_doclinks_storable_scope hands the marker to its
caller to free (lsp_surface.c).
One S8 expectation changes with this: Healthy.cs's reference to Hidden2,
a name Bad.cs declares where no namespace can be established (a Q record),
is now a graph gap rather than an edge -- as it is when Bad.cs's scope is
taken. The namespace checks of S8 (Shadow, Good.Lurker) are unchanged.
Test: cs_rejected_scope_contained and cs_rejected_scope_across_runs, whose
shared fixture gains Good.Widget in the refused file and Lib.Widget in an
imported namespace: Healthy.cs's Widget is a graph gap with no edge, and
the stored marker holds the five names.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The scanner names every namespace region it writes (each segment holds at
least one identifier character). The reader took an empty name from the
store all the same: ns_make_path then returns the parent namespace, so the
region nests without deepening it, its types are declared one level up,
and lookup asks the directives of fewer regions than the file has.
parse_region now refuses an empty name field, so such a stored scope fails
the run with bad_scope like any other record this code does not write, and
the next index rebuilds from the files.
Test: cs_damaged_stored_scope runs each damage in turn; the new case blanks
the name of A's region record (one error row, no `missing` row for Target,
then a forced full run that restores the edge).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A C# scope blob written in a run that the reader refuses costs only its own
file in that run (a8c5708d). The blob itself was stored, though, and the next
incremental run read it back as a stored scope the reader refuses, which
fails the layer for the whole run.
The surface row now stores, in place of such a blob, the C# rejected marker
(the tag and one `!` record). Whether the reader takes a blob is decided
where the row is made, by the reader's own record checks on an index of the
blob's own (what makes a blob refused depends on the blob alone); the
doc-link layer asks the language through two new resolver fields,
scope_accepted and rejected_scope (cbm_doclinks_storable_scope). A later run
that reads the marker back treats the file as the run that wrote it: it
declares nothing, its references are graph gaps, it is counted with the
rejected files. A stored scope that does not parse for any other reason
still fails the run. The scope delta of a file whose blob is the marker on
either side is LOCAL only when both are.
The test seam that spoils a file's scope now does it where the scope is
written (internal/cbm/doclink_cs.c), so what is stored carries it too.
Test: cs_rejected_scope_across_runs (a full run with the spoiled file, then
a body-only edit elsewhere and one in the spoiled file: each incremental
step equals a full run, and the stored scope is the marker).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A memo is made for every file that has references, and most files ask few
using steps: its table and arena are now opened by the first step it
keeps (8 KB arena block, 16-entry table), not when the file starts.
Outcomes and work counts are unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A name that no enclosing type or namespace has is looked up in the using
directives of each scope level, and a name some namespaces declare walked
the min(postings, directives) of the level for every reference. A file with
r references to such a name under k directives cost r x k.
The using step of a lookup -- what one region's directives, and at the
global namespace the unit's, give one query -- is now remembered for the
pass: the references of one file (a per-file working state the doc-link
layer hands to the resolver: file_begin / file_end), and the build's passes
over using directives and base lists. The key holds everything the step
reads: the file, the region and unit asked, the context's visibility
(product code, unit, assembly) and the query. A memo that runs out of memory
stops remembering; the lookups stay exact.
Inside a lookup, the alias and using walks stop at a level's second
candidate: the result is ambiguous whatever the rest would bring. Which
candidate came first, and whether there was one, never depends on the
directives skipped; only the ambiguity tally of the log line (why) may count
such a reference under another kind.
There is no cap: every outcome is what it was.
Tests: cs_lookup_repeated_name_work (k 60 -> 240 and r 40 -> 160: 236 -> 900
steps, 3.8 x; without the memo 3004 -> 41444, 13.8 x),
cs_lookup_ambiguous_stops (one ambiguous reference under 50 -> 200 imports
that declare it: 17 -> 21 steps; without the stop 65 -> 219).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
make -f Makefile.cbm lint-ci failed on code of the checkpoint:
- doc_links_msbuild.c: outside a seam build, msb_fail_value_alloc and
msb_fail_prop_insert were stubs that return false, so cppcheck reported
every condition that calls them as always false or always true
(knownConditionTrueFalse, five sites). The calls are now compiled only
with CBM_ENABLE_TEST_SEAMS, as the layer's other seams are, and the stubs
are gone.
- mcp.c: in one configuration of vendored yyjson.h's fallback typedef chain
cppcheck takes uint32_t for unsigned short and reports the bounds of the
U+E0000 tag block as out of range (compareValueOutOfTypeRangeError). The
preview's escape check takes unsigned long, which the standard makes at
least 32 bits wide.
The product binary is byte-identical before and after.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>