Skip to content

Commit acb9f52

Browse files
speak-agentspeak-agentclaude
authored
2026.9.28.1: eight reports (#717, #718, #720, #722-#726), one download progress, and an index floor that is a tip (#727)
* docs: eight reports after 2026.9.27.1, the design and the implementation plan * fix: a host-module lib root is ordered with the units of its package (#720) The lib root was placed at the head of the host-module compile list before the list was sorted by imports, so a lib root that imports a sibling unit was compiled first and failed. The lib root is now the first node of the same sort; it is emitted first whenever it imports nothing of its package, so existing packages keep their order. e2e 807 covers an explicit and a conventional lib root. * fix: an index that requires a newer mcpp is a closing tip, not an error The read site printed the E0006 text as error: at the start of every run that read such an index, including runs that then resolved every package elsewhere and exited 0. The fact is now recorded without printing: - a run that fails carries the E0006 text in the message that stops it; - a run that refreshed an index and met a floor ends with one tip: line, printed after the command's output (closing notices, mcpp.ui), and an envelope reports it as the note MCPP_INDEX_REQUIRES_NEWER_MCPP; - mcpp self doctor lists every index whose floor this mcpp does not meet; - a tree recorded as unusable answers no later lookup either; - the E0006 upgrade note starts on its own line. Unit tests for the read site, the hint, the notices; e2e 812. * docs: regenerate the design-record index * W1 (mcpp#725): a rooted workspace reaches its own path dependency, and -p resolves the package first A rooted workspace (root [package] + [workspace]) built as itself never set state.wsManifest / state.runtimeWorkspaceRoot, so a member reached through the root's own [dependencies] path entry was loaded as an ordinary path dependency: its x.workspace = true entries were refused or silently unresolved, and it received none of [workspace.package] / [workspace.build]. The workspace context is now set in that branch too, before any dependency is loaded, matching the member-switch branch and satisfying "a member is a member however it is reached" (graph.cpp's depIsMember). -p, --package <NAME> promised a package but both resolvers (manifest.cpp's inline loop and project.cppm's resolve_member_dir) matched only a member's directory basename or path. They are now one function in project.cppm, resolving in order: a member's qualified name (<namespace>.<name>), then its bare package name (refused, naming every match, when shared by two or more members), then its directory path or basename (kept as a fallback; a duplicate basename keeps today's first-match selection but now warns naming the others). A value that is one member's package name and a different member's directory selects the package, with a warning naming the other member. SPEC-004 §9 item 1 names the missing position and states that -p resolves the package identity first; docs/07 §5.3 (en, zh) and the four `-p` help strings state the resolution order. Tests: six unit tests for the resolver in test_workspace_inheritance.cpp (package/path/basename all selecting one member, same name under two namespaces, package-outranks-directory with a warning, duplicate-basename warning, the "not found" listing, and the unaffected no-filter cases); e2e 805 (a rooted workspace's own path dependency, with a non-latest pinned version, an omitted package.version, a [workspace.build] flag, and a shared root [toolchain], served from a project-local index so the build touches no network) and e2e 806 (-p's three resolution steps end to end). Both e2e scripts fail on the released 2026.9.27.1 binary and on 2026.9.26.1, and pass with this fix. Co-authored-by: speak-agent <agent@mcpp-community> * W6 (#723): one destination, one content, one writer for a deploy target Two or more source paths for one deploy destination no longer collide at planning. `add_deploy` (src/build/plan.cppm) merges them into one `DeployFile` entry instead of refusing a second source path; a `BuildPlan` whose deploy has exactly one source still produces the byte-identical `build.ninja` line it always has. The merged destination becomes one `stage_file` edge with every source as an input (src/build/ninja_backend.cppm), and `mcpp stage` places it once all sources agree byte-for-byte (`stage_files`, src/build/stage.cppm), otherwise failing and naming every source and the destination. `cmd_stage` (src/cli/cmd_build.cppm) accepts one or more source positionals. One destination, one writer: `place-dlls`'s post-link DLL placement (src/pack/pack.cppm `place_runtime_dlls`, invoked from src/cli/cmd_publish.cppm `cmd_place_dlls`) now receives the set of names the merged deploy list already places beside a program, computed in ninja_backend.cppm and passed through a per-edge `$placed` ninja variable so the generated graph stays stable. `place_runtime_dlls` skips those names instead of overwriting them, comparing content and reporting a difference as a warning. SPEC-007 R4.2 and R4.3 are amended to state the content check and the single-writer rule; docs/04 (en, zh) is corrected to match. e2e 810 covers the merge and the build-time refusal; e2e 811 (`# requires: windows`, not run here) covers the single-writer rule; e2e 646's collision case is updated for the new build-time message. Unit tests cover `stage_files`'s multi-source behaviour and the deploy-list skip/warn logic in `place_runtime_dlls`. Co-Authored-By: Claude <noreply@anthropic.com> * 2026.9.27.2: on Windows an xlings invocation leaves the process environment as it found it and starts in the registry's home (#726) On Windows, build_command_prefix prepended the registry's subos/default/bin to the process PATH and set XLINGS_HOME process-wide, and ran xlings in mcpp's working directory. After a build installed a payload, every action found xim:llvm's cl/link/lib/rc shims in front of MSVC's tools, and vcpkg's compiler detection failed; a project with a .xlings.json at its root also received the shims of mcpp's toolchain and payloads in its own SubOS. ScopedInvocationEnv now applies XLINGS_HOME, the scope variables and the PATH prefix for the invocation and restores them afterwards, and the Windows prefix starts with `cd /d "<home>" &&`, as the POSIX prefix does. Refs #726. * T3 (#724 W3/W4/W5): a device source is not a compile unit; a failed build program's own diagnostic survives; emit writes no project file W3 (src/build/plan.cppm): the compile-unit loop now skips SourceKind::Device graph units, so build.ninja carries no dead cxx_object edge for a rule-claimed device source, and compile_commands.json / the S1 document agree without their own filter (unit_invocations already excluded only NASM; nothing else needed to change). The source stays in `watch` and still reaches the package's build program through MCPP_DEVICE_SOURCES, since both read the manifest's sources glob directly, not plan.compileUnits. Checked every other consumer of plan.compileUnits (prepare/plan.cpp's dependency-cache collection keys by path, not by index, so it is unaffected beyond a smaller artifact set for a package with device sources). W4: a package whose build program failed under `emit`'s plan_only records MCPP_BUILD_DATABASE_PROGRAM_FAILED and applies none of its directives (state.cppm: new PrepareState::programFailedPackages, set at both call sites in target_side.cpp and features.cpp). The device-source orphan check in target_side.cpp now skips such a package outright, instead of reading every device source as unclaimed and failing the whole member. Also: notes a phase recorded before prepare_build's own failing return are no longer silently dropped (driver.cpp: a thread_local sink in the same per-run-sink style as mcpp::build::refusal, exported as mcpp::build::take_notes_on_failure); cmd_build.cppm's emit failure path folds any such note into the one diagnostic SPEC-005 R5.2 allows a wholly- failed member (path stays the member's mcpp.toml, exactly one entry), so the true cause is not lost behind a downstream symptom without violating that invariant. hasProgram's existing exists(build.mcpp) check is now correct by construction, since a failed program's package never reaches it. W5 (src/build/prepare/xlings.cpp): ensure_project_index_dir's two calls under a private work_dir are collapsed into the one call the ownerRoot==workRoot branch always made, targeting workRoot in every case. Previously the runtime-environment half (deps/subos/workspace) went to runtimeSelection.ownerRoot, which is always the real project root regardless of emit's private work_dir -- so `emit build-database` wrote <root>/.mcpp/.xlings.json into a project that declares [xlings] deps. SPEC-005: R3.7 names device sources beside NASM units (both absent from S1 and compile_commands.json, for different reasons -- NASM is a compile unit excluded from export, a device source is never a compile unit at all). R5.2 gains the sentence that a check whose premise is a build program's directives does not run for a package whose program failed in this pass. R2.1 needed no change. Tests: e2e 808 (device source: no dead ninja edge, absent from both databases, and the rule still compiles it and the build still runs), e2e 809 (a device source plus a build.mcpp that does not compile: PROGRAM_FAILED with path build.mcpp, no device-source mention, package still described), e2e 817 (688's project-tree digest repeated on a stub-xlings fixture with [xlings] deps: byte-identical tree, no .mcpp/.xlings.json, no write-project effect, and the private work directory does gain one naming the dependency). Each fails against the released 2026.9.27.1 binary and passes on the fresh build. Full regression: all 15 emit/build-database e2e scripts, 798, and four more that exercise the compile-unit loop (asm/GAS, NASM, object-path-collision, multi-module) all still pass; `mcpp test` (130 unit tests) passes. * docs: #726 and #727 join the round (W13); the split moves to the last stage * W10 (#724): the S1 document names what a rule generates (S1 0.3.0) A set's ide.generated lists each output of its package's source-role actions (header or source, with the step's id, inputs, arguments and work directory) and each generated include directory its units name, with the path the document names and the path a mcpp build of the same selection writes. The profile version is 0.3.0 (Sunrisepeak/ mcpp-language-server#28); compile_commands.json is unchanged. SPEC-005 R3.12, docs 50; e2e 815, e2e 688 reads the new version. * W11: one renderer for every acquisition; plain output off a terminal - ProgressBar prints one start line and one finish line when stdout is not a terminal: no carriage return, no erase sequence, no repaint per frame; an item that does not complete says so instead of reporting it done. - The index refresh runs through xlings interface update_packages and draws its progress and download events with the same renderer; an xlings that emits none shows its status line as before, and its terminal text no longer reaches the output. - The clone of a git dependency passes --progress and draws the download phase, read as it is redrawn (run_streaming_bounded gains an opt-in rule that a lone carriage return ends a line); the output is kept whole for the failure message, and a clone silent for fifteen minutes is stopped. - The sandbox bootstrap's hand-drawn spinner is the shared bar. e2e stubs accept the interface refresh; unit tests for the git progress parser, both render modes and the line splitting; e2e 816; docs 09. * W11: draw one bar per index-refresh phase, not one per event message * T7 (#717 W8): a graph-wide dialect switch under a target condition [target.<selector>.build] dialect_cxxflags is now accepted, parsed into a new ConditionalConfig member kept apart from BuildInputs (the key is graph-wide, not a per-package additive input), and merged into the same BuildConfig::dialectCxxflags every consumer already reads: the std BMI prebuild, the scan, every TU and the fingerprint. Only the root of the build renders it; a dependency's own value (conditional or not) reaches no command and is now excluded from its own fingerprint contribution, matching the rule that a key enters a fingerprint only where it reaches a command. The build-program directive is deliberately not added. SPEC-004 SS3.1 and SS9 item 10 state the new rule; docs/04 and docs/zh/04 document the conditional form (since 2026.9.28.1). Unit tests cover parsing, the emptiness gate, and root-only resolution order. e2e 813 covers reach into the std BMI/scan/TUs, a non-matching selector, the A-B-A std BMI rebuild, and a dependency's key being fingerprint-inert; it fails on the released 2026.9.27.1 binary and passes on the fresh one. * W11: an index sync bar names its repository * W6: place-dlls decides the other writer's DLLs from the directory The one-writer rule passed the deploy list's names to place-dlls on its command line. The plan's deploy set reads runtime search directories that a prepare action fills, so it differs between the first and the second plan; the command changed and every build after the first re-ran the placement (e2e 797, found by a differential run against 2026.9.27.1). place-dlls now treats a DLL beside the program that it did not place, and that a runtime search directory also offers, as another writer's; the command is the one 2026.9.27.1 wrote. * chore: version 2026.9.28.1 in both places (mcpp.toml and MCPP_VERSION) * T8 (#718 W9): the CRT model is a property of the MSVC ABI, not of cl.exe Every MSVC-ABI row now receives the same CRT model: cl spells it /MT or /MD, clang++ targeting *-windows-msvc spells it -fms-runtime-lib=static or =dll. One helper (msvc_abi_crt_word, dialect.cppm) decides the word for the translation units, the std/std.compat BMIs and the link command alike, closing #649 E10 (a compile-only flag never reached the clang driver's own link-time choice of -defaultlib:). toolchain-coupled (the dynamic CRT, with the toolset's own vcruntime140.dll/msvcp140.dll staged beside the artifact) is now the default for every role on this ABI (dist::msvc_abi_default_contract, ContractStatement::msvcAbiDefault). A toolset with no VC\Redist\MSVC directory defaults to host-coupled silently and refuses an explicit toolchain-coupled, naming the missing directory (prepare/plan.cpp). A free-form CRT word in cxxflags/dialect_cxxflags is checked against the resolved model: agreeing is a warning, contradicting is a refusal (dialect::check_crt_word, wired in prepare/scan.cpp). The toolset's redistributable directory is carried as its own Toolchain field (msvcRedistDir), populated for cl from vc_redist_dir and for the LLVM row from its sysroot's tools directory (vc_redist_dir_for_tools_dir) rather than from linkRuntimeDirs, which holds LLVM's own runtime directories on that row. The staging gate and the mcpp run/test search path both read this field, gated on the MSVC ABI rather than on which compiler is in use. mcpp pack carries the staged DLLs by default; an explicit --mode system now resolves a defaulted (never-declared) toolchain-coupled contract to host-coupled instead of refusing. e2e 703 is inverted to the new default; e2e 814 covers the LLVM row's import table, staged DLL, clean-PATH run, self-contained round trip, BMI switching (A, B, A) and pack modes (Windows-only, unverified here). Unit tests cover the CRT-word derivation, the free-form-word check, the MSVC-ABI default/redistributable resolution and a compute_flags-level property test across both rows; the link-line half of that test is gated on mcpp.platform.is_windows, since link_shape resolves LinkShape::PeLld only when current_link_host() is Windows and a Linux-built mcpp cannot reach that branch regardless of the plan's target triple. docs/20, docs/zh/20 and SPEC-006 record the new default and the upgrade; docs/04 needed no change (it only points at docs/20). 131/131 unit tests pass on Linux (gcc 16.1.0 and llvm 22.1.8 rows); docs structure/style checks pass. * docs(specs): SPEC-004 1.9, SPEC-005 1.5, SPEC-006 0.3, SPEC-007 0.4, and the index * docs(changelog): 2026.9.28.1 * docs: implementation readings of the round (13.5) * docs(50): the refusal token msvc-redist-unavailable (#718) * chore: xlings pin 2026.9.28.1 (interface protocol 1.2, the progress events) * review: every set of a package names what its build program generates; place-dlls comment matches its command; docs/20 names the --mode system refusal * T6 (mcpp#722, W7): split phase13_finish (plan.cpp) into sub-steps Verbatim extraction along the sections its own banners already name: prebuilt dependencies, link forms, make_plan, the C++ runtime checks, graph/schedule, declared build-graph actions, assembly units, Windows resources, the global dependency cache, mcpp.lock, runtime provider overrides, ABI enforcement, resolution.json, and the empty-link check. Longest resulting function: 356 lines (step13_build_graph_actions). * T6 (mcpp#722, W7): split phase4b_graph_worklist (graph.cpp) into sub-steps The worklist step for one item is split into identity resolution, the already-resolved / identity-adoption handling (with its own version-merge sub-step), acquiring a fresh dependency's source and manifest, and finalizing it (recording the package, recursing into children); the per-item locals that cross those boundaries move into a phase-local struct, WorklistItemCtx, the same PrepareState pattern one level deeper. The post-loop cycle check is its own function. Preamble closures that captured only `state` (or nothing) become static file-scope functions, called with an explicit PrepareState& where they used to close over it; this also fixes the one comment that had gone factually stale (activateFeatures's group banner said 'defined as local lambdas, not file-scope functions' -- now they are file-scope, and are still safe because a static function carries no external linkage into the module's exported interface). Longest resulting function: 300 lines (step4b_identity_version_merge). * T6 (mcpp#722, W7): split phase6_features_and_host_tools (features.cpp) into sub-steps Along the sections its own banners already name: feature activation, device extensions and rule application, the graph's [xlings.workspace] provisioned before build.mcpp, host-module registration, host-tool provisioning, the dependencies' build programs, and capability binding. aggregatedRequest (needed by two of these sections) is promoted from a local lambda to a file-scope function of PrepareState&. Two scoping braces that had no matching close within their own section (opened to wrap several sections at once) are dropped as redundant once the content is distributed across separate functions, each of which supplies its own scope. Longest resulting function: 425 lines (step6_provision_host_tools). * T6 (mcpp#722, W7): split phase9_target_side (target_side.cpp) into sub-steps Gather the graph's target-side candidates into a phase-local struct (TargetSideGather, the PrepareState pattern one level deeper), then resolve and realise [c-abi], broadcast the include set, check kernel-abi interfaces and layer requirements, apply the layer-conditional config (L1b), decide each dependency's link form (#519), define the graph_package_entry closure, run the root build.mcpp (L3), require every device source to reach an action, and handle re-run inputs (R1.3). Two scoping braces that wrapped several sections at once are dropped as redundant, as in the two previous files. Longest resulting function: 404 lines (step9_kernel_abi_interfaces_and_requirements). * T6 (mcpp#722, W7): split phase4a_graph_load (graph_load.cpp) into sub-steps The closures phase4a_graph_load assigns onto state (each captures only state) split into two groups: split/identity closures, and candidate selection closures. LoadedDep is hoisted to file scope so it stays visible to state.loadVersionDep, which remains where it was. state.loadVersionDep itself (509 lines) is not split further: its six local closures (readLuaContent, findRawInstalled, installedLayoutMatchesIndex, revisionIsCurrent, findCompleteInstalled, markInstalled) mutually capture nine-odd shared locals by reference: hoisting them to free functions would mean threading all of that through explicit parameter lists for one closure, judged higher risk than benefit within this round; noted as a residual in the T6 report. * review: the round's CI failures and the three review angles CI (PR #727): - e2e 190/191 select the program by name; bin/ also holds the staged redistributable DLLs on an MSVC-ABI row (#718). - A DLL found in a runtime search directory yields to a declared deploy of the same name, and a difference is warned at planning, where a successful build shows it (SPEC-007 R4.3, e2e 811; e2e 818 is its Linux-hosted form). - build.mcpp that imports only a build rule compiles in the build directory under GCC (e2e 807). - The runtime environment half of .xlings.json goes to the runtime's owner again; only plan_only redirects it to the planning directory (e2e 205, 817). Review: - Every set of a package names what its build program generates (R3.12). - A dependency's contradicting CRT word is refused; debug CRT words are refused; the agreeing-word message names the key of the dynamic model; the dependency cache key carries the CRT word. - PE deploy destinations compare without case; MSVC version directories compare numerically; a missing deploy source is named as missing. - A failed index refresh draws "did not complete"; an automatic refresh that exhausts its retries warns; the floor tip names the version and the install-aware upgrade. - -p compares paths as paths; the help names the qualified form. * docs: global review and the first CI run of the round (13.6) * T6 (mcpp#722, W7): split phase1_toolchain_spec_and_axes (toolchain.cpp) into sub-steps Split at phase1's own banner boundaries: the closures phase1 assigns onto state, the target/--static override resolution, and the device axis plus the L1 conditional-section merge. phase2_define_toolchain_resolver is not split: it is a single stored closure, state.resolve_target_toolchain, whose ~1000-line body is one sequential toolchain-resolution flow with dozens of interdependent locals -- the same category of residual as state.loadVersionDep in graph_load.cpp, noted in the T6 report. step1_target_and_static_overrides (524 lines) is a smaller residual of the same kind. * T6 (mcpp#722, W7 follow-on): split phase0_manifest_and_workspace and phase3_xlings_before_graph Both were found over ~400 lines by the same sweep that produced the seven functions #722 named (the issue's own "any other function under src/build/prepare/ over ~400 lines"), and split cleanly along their own banners: phase0's own "Workspace handling" section becomes step0_workspace_handling (108 lines; phase0 itself drops to 372); the host-toolchain closures phase3 assigns onto state, plus the index-refresh section, become step3_define_host_tc_closures_and_refresh_index (298 lines; phase3 itself drops to 235). * T6 (mcpp#722): add the function-size gate, verified but not wired into CI yet .github/tools/check_function_sizes.sh runs clang-tidy's readability-function-size (LineThreshold=400) over the compile database mcpp produces for its own LLVM build (mcpp build --toolchain llvm@22.1.8), restricted to files under src/build/prepare/. clang-tidy is not part of the plain xim:llvm payload; it ships in the sibling xim:llvm-tools package at the same version, which the script locates under the xlings store. Measured: at 7ccbc8d (before this round's split) it reports 10 functions over 400 lines -- the seven #722 named, plus phase0_manifest_and_workspace, phase11_scan and phase3_xlings_before_graph, found by the same sweep. After this round's split it reports 6: step6_provision_host_tools (421), phase4a_graph_load (518, its loadVersionDep closure), phase11_scan (773, untouched), step9_kernel_abi_interfaces_and_requirements (401), step1_target_and_static_overrides (522) and phase2_define_toolchain_resolver (1006, untouched) -- see the T6 report for why each remains. Not wired into CI next to check_file_lengths.sh: it does not yet pass, so adding the workflow step now would land a gate red on day one. Wire it once the remaining residuals are split in a follow-up; the file gate stays the only enforced one for this round, per the design's own fallback for a working tool over an incomplete split. * prepare: mcpp.lock and resolution.json move to records.cpp (plan.cpp under the file-length limit after the merge; byte-identical on the seven fixtures) * T6 (mcpp#722, W7 residual 1/6): split step6_provision_host_tools (features.cpp) The per-tool body of the outer loop (~421 lines) becomes HostToolCtx, a phase-local struct following the WorklistItemCtx / TargetSideGather pattern, and six sub-steps: target resolution, the override/self-request checks, the store-key/cache-hit resolution, the sub-build, and publish. The `record` closure the single-function version captured per tool is a named helper called from all four sites that used it (an override, a cache hit, a deferred plan-only tool, and a freshly built one). Verified: byte-identical on the seven fixtures; check_function_sizes.sh reports no finding in this file. * T6 (mcpp#722, W7 residual 2/6): split the loadVersionDep closure (graph_load.cpp) phase4a_graph_load's stored closure state.loadVersionDep (~518 lines) is split the way phase2's single stored closure is: a local struct (LoadVersionDepCtx) holds the parameters and the locals more than one section reads, and the body becomes three named steps (locate an already-installed copy, fetch one if none is installed, then read its manifest) the closure calls in sequence. The stored closure stays the entry point with the same signature, so its own recursive call for a preinstall hook's dependencies is unchanged. Two of its local lambdas (readLuaContent, findRawInstalled) are read from two of the three steps (the initial read and a later re-check, or the initial probe and the post-install one) and become named helpers for the same reason `record` did in the previous commit; a third (markInstalled) is promoted for the same reason though it has only one call site, since the closure it replaced no longer has anywhere to live once the body it was local to is split. Verified: byte-identical on the seven fixtures; check_function_sizes.sh reports no finding in this file. * T6 (mcpp#722, W7 residual 3/6): split phase11_scan (scan.cpp) Split at its own section boundaries into eight named steps (source scan and validation, the dependency-standard scope check, the dialect-flag gate, the MSVC CRT-word check, the package std-module source, the Apple SDK C++ runtime rule, the std-module availability gate, the fingerprint computation, and the std-module prebuild). needsStdModule is the one value several steps read; it is computed by the first step and passed as a plain bool parameter rather than through a struct, since it is the scan's only shared local. Verified: byte-identical on the seven fixtures; check_function_sizes.sh reports no finding in this file. * T6 (mcpp#722, W7 residuals 4-5/6): split step1_target_and_static_overrides and phase2_define_toolchain_resolver (toolchain.cpp) step1 (~522 lines): a phase-local struct (TargetOverrideCtx) holding the parsed triple, its vocabulary-table row and the section lookup carries seven sub-steps through one `--target` request's resolution (resolve the request, validate its tier, the Apple SDK check, the wasm shared-lib check, the host_can_serve diagnosis, capturing the display name and canonicalising, and the row-pin/capability check). The `apply_target_section` closure is a named helper, called from the resolved-request branch and from the host-row branch that follows it. phase2 (~1006 lines, a single stored closure, state.resolve_target_toolchain): a phase-local struct (ToolchainResolveCtx) holds only the parsed spec and the Windows installed-toolset probe -- the two locals the branch-selection half of the closure shares. Every other local (the first-run defaults, the explicit-spec resolution's own payload/frontend locals, and so on) stays local to the one step that declares it, as it was in the single function. The closure becomes twelve named steps: parsing the spec, each arm of the compiler-resolution if/else-if chain, the Windows-first-run persist, toolchain detection, the retargetable-driver fixup, the MSVC toolset bind, the Windows runtime identity, the MSVC-ABI-without-MSVC repair, and the musl default linkage. The stored closure stays the entry point with the same signature, so its own recursive call (the target pass) is unchanged. Verified: byte-identical on the seven fixtures; check_function_sizes.sh reports no finding in this file. * T6 (mcpp#722, W7 residual 6/6): split step9_kernel_abi_interfaces_and_requirements (target_side.cpp); the gate's own false failure at zero findings step9 (~401 lines) splits into five named steps at its own section boundaries: the kernel-abi interface enumeration, the layering and requirement checks (over TargetSideGather's `requirements`), the pin-and-linkage diagnostics, the same-OS check and report, and the platform-sdk closure visibility check. No phase-local struct is needed: each step reads and writes only `state` (plus `gather` for the one step that needs its `requirements`). This was the last of the six residuals check_function_sizes.sh reported at the base of this round; with it split, the gate ran clean over the whole of src/build/prepare/ for the first time -- and immediately failed for an unrelated reason: `--warnings-as-errors='*'` makes clang-tidy exit non-zero on the two pre-existing readability-function-size findings in the bundled third-party modules/libs/src/json/json.hpp, which the script's own `relevant` filter already excludes from the pass/fail decision but which its exit-code fallback then reads as "a diagnostic tool problem" -- a false failure this repository's own tree could not previously reach, since no earlier revision had zero findings under src/build/prepare itself. Dropping the flag leaves clang-tidy's exit code meaningful only for an actual tool failure (a bad compile command, a crash), which a plain warning never produces; `relevant` remains the sole pass/fail signal, as the surrounding comments already say it is. Verified: check_function_sizes.sh reports "ok" over the whole of src/build/prepare/; byte-identical on the seven fixtures. * ci: the function-size gate runs after the LLVM self-build; clang-tidy is resolved at the version that wrote the compile database * docs: #722 completed, the function-size gate in CI (changelog, 13.6) * review: the function-size gate is run by hand until CI builds mcpp with clang (#729); a generated deploy source is compared after the link only - ci-linux.yml: the gate step comes out again. The LLVM toolchain job's llvm@20.1.7 self-build has never completed (libc++ 20's std module hides directory_iterator's comparison) and the step reads the resolution line, not the build's exit status; clang-tidy crashed over the partial database. Recorded as #729. - check_function_sizes.sh: a finding is a diagnostic line ending in the bracketed check name; a crash dump no longer reads as a finding. - prepare/plan.cpp: a declared deploy source that an action writes is left to the post-link comparison; at planning it may hold the previous build's bytes. --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> Co-authored-by: speak-agent <agent@mcpp-community> Co-authored-by: Claude <noreply@anthropic.com>
1 parent b439fd9 commit acb9f52

115 files changed

Lines changed: 10490 additions & 3147 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/docs/2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md‎

Lines changed: 1417 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
subject: plan
3+
status: active
4+
---
5+
6+
# Eight reports after 2026.9.27.1: implementation plan
7+
8+
This record implements `2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md`
9+
(the design, revision 3, all decisions settled). The design fixes what is built. This
10+
record fixes the following:
11+
12+
- the order;
13+
- which files each task owns;
14+
- the repositories involved and the order of their releases;
15+
- how each step is verified.
16+
17+
## 1. Readings that shaped the plan
18+
19+
- **xlings emits no progress events for an index sync (measured).** The command is
20+
`xlings interface update_packages --args '{}'` (xlings 2026.9.27.1).
21+
- It emits heartbeats and one result, and no progress event.
22+
- It also writes its terminal progress text (`[1/7] awesome::xim.lua` followed by
23+
an erase sequence) onto the NDJSON stream. That text is not JSON, and the
24+
xlings interface protocol (`docs/spec/interface-ndjson-v1.md`) does not allow
25+
it.
26+
- W11 therefore needs an xlings change and an xlings release before the mcpp
27+
release.
28+
- **mcpp-language-server.** speak-agent has read access only, so its S1 change is
29+
proposed from a fork.
30+
- **The next e2e number is 805.** Unit tests live in `tests/unit/`.
31+
32+
## 2. Repositories and their order
33+
34+
| Order | Repository | Pull request | Content | Release |
35+
|---|---|---|---|---|
36+
| 1 | openxlings/xlings | one | interface mode emits `progress` events for an index sync and keeps terminal text off the NDJSON stream | yes; the date version of the day |
37+
| 2 | Sunrisepeak/mcpp-language-server | one, from a fork | S1: the generated-output record (design §4.4, D6) | no, a specification only |
38+
| 3 | mcpp-community/mcpp | one: #727, which also carries #726's fix (W13) | W1 to W13, docs, specs, CHANGELOG, version, xlings pin | yes |
39+
| 4 | openxlings/xim-pkgindex | the bot's bump pull request | mcpp's new version | merged by a maintainer account |
40+
| 5 | mcpplibs/mcpp-index | one, if its CI pin or `latest_mcpp` must move | index consumer pins | no release; the index publishes on merge |
41+
42+
The mcpp pull request pins the xlings release of row 1 (`kXlingsVersion`), and the
43+
release pull request carries that pin.
44+
45+
## 3. Tasks, owners and dependencies
46+
47+
The work uses one integration branch, `feat/eight-reports`, in the worktree
48+
`mcpp-eight`. Each task has its own worktree, branched from the integration
49+
branch, and is merged back when its criteria pass.
50+
51+
| Task | Steps | Files owned (smallest hunks elsewhere) | Depends on |
52+
|---|---|---|---|
53+
| T1 | W1 | `src/build/prepare/manifest.cpp`, `src/project.cppm` (member resolution), `src/cli.cppm` (`-p` help), `docs/07` (en, zh), SPEC-004 §9, `tests/unit/test_workspace_inheritance.cpp`, e2e 805 and 806 | none |
54+
| T2 | W2 | `src/build/prepare/features.cpp` (host-module unit order), e2e 807 | none |
55+
| T3 | W3, W4, W5 | `src/build/plan.cppm` (the unit loop only), `src/build/prepare/target_side.cpp` (the device-source check), `src/build/prepare/driver.cpp`, `src/build/prepare/xlings.cpp` (the project index file), `src/cli/cmd_build.cppm` (the emit failure path), SPEC-005, e2e 688 extended, e2e 808 and 809 | none |
56+
| T4 | W6 | `src/build/plan.cppm` (`add_deploy` only), `src/build/stage.cppm`, `src/cli/cmd_build.cppm` (`cmd_stage` only), `src/build/ninja_backend.cppm` (the stage and `place_dlls` edges), `src/pack/pack.cppm` (`place_runtime_dlls`), SPEC-007 R4.2 and R4.3, e2e 810 and 811 | none |
57+
| T5 | W12 | `src/pm/package_fetcher.cppm`, `src/pm/index_contract.cppm`, `src/xlings/xlings.cppm` (`update_index` reporting), `src/ui.cppm` (closing notices), `src/doctor.cppm`, `docs/09` and `docs/50`, e2e 185 updated, e2e 812 | none |
58+
| T6 | W7 | `src/build/prepare/*.cpp` (phase functions), `.github/tools/` (the size gate), `tests/unit/test_prepare_helpers.cpp` | every other task merged (the last step; see the design, §11) |
59+
| T7 | W8 | `modules/manifest/src/toml.cppm`, `modules/manifest/src/types.cppm`, `src/build/prepare/scan.cpp` and `target_side.cpp` (dialect resolution), `src/build/prepare_inputs.cppm`, SPEC-004 §3.1 and §9, e2e 813 | T1 to T5 merged |
60+
| T8 | W9 | `modules/toolchain-model/src/dialect.cppm`, `src/build/flags.cppm`, `src/build/prepare/scan.cpp` (std-module CRT), `src/build/distribution.cppm`, the toolchain redistributable field (`src/toolchain/msvc.cppm`, the LLVM row's sysroot resolution), `src/pack/pack.cppm` (contract), `docs/20` and `docs/04`, unit tests, e2e 814 (Windows) | T1 to T5 merged; its free-form word rule reads T7's list at merge |
61+
| T9 | W10 | `src/build/build_database.cppm`, SPEC-005 §3, e2e 815 | T3; the S1 text (Sunrisepeak/mcpp-language-server#28) |
62+
| T10 | W11 | `src/ui.cppm` (terminal and non-terminal rendering), `src/xlings/xlings.cppm` (index refresh through the interface), the git fetch in `src/build/prepare/fetch.cpp` and `graph.cpp`, the sandbox bootstrap, `docs/09`, unit tests, e2e 816 | T5; the xlings change (X1), with its release before mcpp's |
63+
| X1 | xlings | `openxlings/xlings`: the interface event stream for `update_packages` | none |
64+
| L1 | mcppls | `docs/specs` S1 addition | none |
65+
66+
T1 to T5, X1 and L1 have no dependency on one another. At most three subagents run
67+
at once. The author takes T2 and the merges, and runs the integration build and
68+
the full test suites.
69+
70+
**Rules for parallel work.** These come from the 2026-09-12 and 2026-09-26 records.
71+
72+
- **No global configuration change.** No task changes `~/.mcpp/config.toml` or any
73+
other global configuration. A toolchain is selected per fixture or per command.
74+
- **No broad `pkill -f`.** No task kills processes by a broad `pkill -f` pattern.
75+
- **One build per worktree.** No two builds run in one worktree at once.
76+
- **Clean up after merging.** A merged task's `target/` is removed.
77+
78+
## 4. Verification
79+
80+
**Per task.**
81+
82+
- The fresh binary passes `mcpp test` and the task's own e2e scripts.
83+
- Each new criterion is also run with the fix removed, and must then fail.
84+
85+
**Integration.**
86+
87+
- A full `mcpp test`.
88+
- The e2e suite on Linux, through `tests/e2e/run_all.sh` with the fresh binary.
89+
- The golden fixtures of the #719 decomposition.
90+
- CI on every platform through the one pull request.
91+
92+
**After the release.**
93+
94+
- **A sandbox.** `xlings subos new eight`, then `xlings subos use eight --sandbox
95+
--cmd ...`, with both mcpp and xlings on the CN mirror. The sandbox installs the
96+
released mcpp by its release path and runs one probe per step. The probe is
97+
passed in as base64, and each probe directory is removed at the start of its
98+
section.
99+
- **A control.** The same script runs against 2026.9.27.1, where exactly the fixed
100+
criteria must fail.
101+
- **The index ecosystem.** mcpp-index's validation sweep runs against the new
102+
release.
103+
104+
## 5. Release
105+
106+
The version is the date version of the release day. The xlings pin moves to the
107+
xlings release of row 1.
108+
109+
1. After the release workflow starts, every archive and its sidecar are uploaded
110+
to GitCode with the local `gtc` as soon as each appears on the GitHub release.
111+
2. Each GitCode asset is verified by a GET with a byte comparison.
112+
3. The xim-pkgindex bump pull request is merged with the maintainer account, and
113+
its state is read back afterwards.
114+
4. The release is complete when `pkgs/m/mcpp.lua` on the index's `main` has
115+
`latest` pointing at the release.

‎.agents/docs/README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
1818
---
1919
```
2020

21-
311 records.
21+
313 records.
2222

2323
## By subject
2424

@@ -56,6 +56,7 @@ Records that declare one. Everything else is listed by date below.
5656

5757
### plan
5858

59+
- [Eight reports after 2026.9.27.1: implementation plan](2026-09-27-eight-reports-implementation-plan.md) — active
5960
- [#690: implementation plan](2026-09-25-issue-690-implementation-plan.md) — landed
6061
- [工具链选择与载荷可信度:实施计划](2026-09-24-toolchain-selection-implementation-plan.md) — landed
6162
- [openkal 生态:完整性收尾与验收方案](2026-09-21-openkal-ecosystem-completion-and-acceptance.md) — active
@@ -82,6 +83,7 @@ Records that declare one. Everything else is listed by date below.
8283

8384
### triage
8485

86+
- [Eight reports after 2026.9.27.1: what each one is, where it belongs, and one optimisation plan](2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md) — active
8587
- [#685、#687 与 Windows clang 的 MSVC STL:三个问题的归属,以及工具链载荷的规范化](2026-09-24-685-687-msvc-stl-and-toolchain-payloads.md) — landed
8688
- [运行时绑定方案 v3:让 mcpp 真正安装它所声明的运行时](2026-09-17-runtime-binding-multi-repo-plan.md) — landed
8789
- [#662:目标侧由依赖图提供时,编译器的隐式头文件搜索仍指向宿主](2026-09-17-issue-662-graph-target-header-isolation-plan.md) — active
@@ -102,6 +104,8 @@ Records that declare one. Everything else is listed by date below.
102104

103105
### 2026-09
104106

107+
- [Eight reports after 2026.9.27.1: implementation plan](2026-09-27-eight-reports-implementation-plan.md) — active
108+
- [Eight reports after 2026.9.27.1: what each one is, where it belongs, and one optimisation plan](2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md) — active
105109
- [The compile database, `emit build-database`, and #701/#702: triage against the specifications, and one design](2026-09-26-compile-database-and-issue-699-design.md) — landed
106110
- [Issues #693 to #696: triage against mcpp's contracts, and one repair plan](2026-09-25-issues-693-696-triage-and-repair-plan.md) — landed
107111
- [Workspace inheritance, flag scoping and the published form: a unified repair plan (#690)](2026-09-25-issue-690-workspace-build-inheritance-consistency.md) — landed

‎.github/actions/bootstrap-mcpp/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ inputs:
2525
# `package.name`, so one of the two was simply unreachable — and which one
2626
# depended on the machine, which is why CI failed on `compat:lua` on
2727
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
28-
default: '2026.9.27.1'
28+
default: '2026.9.28.1'
2929
cache-target:
3030
description: also restore/save target/ (build artifacts + BMIs)
3131
required: false

‎.github/actions/setup-macos-llvm/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inputs:
1515
# Floor imposed by the index, not a routine bump — see
1616
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
1717
# (two packages named `lua` in one repo need openxlings/xlings#381).
18-
default: '2026.9.27.1'
18+
default: '2026.9.28.1'
1919
image:
2020
description: >
2121
The runner label the job runs on (macos-15, xcode-27). It is part of the
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Guard: no function under the prepare.cppm decomposition grows past ~400
4+
# lines (mcpp-community/mcpp#722, T6 of the 2026-09-27 round).
5+
#
6+
# WHY
7+
#
8+
# check_file_lengths.sh caps each FILE at 2,500 lines. It says nothing about
9+
# a single FUNCTION inside a file that stays under the cap while one phase
10+
# function alone climbs back past a thousand lines and closes back over the
11+
# ~180-local shape prepare.cppm was split to remove in the first place (see
12+
# that script's own header, and the layout comment atop src/build/prepare.cppm).
13+
# #722 split the seven functions that had grown past ~400 lines into
14+
# sub-steps named after the sections their own banners already used; this
15+
# gate is what keeps a phase function from quietly growing back into one.
16+
#
17+
# THE RULE
18+
#
19+
# Every function defined in a file directly under src/build/prepare/ (or in
20+
# src/build/prepare.cppm itself) stays at or under LINE_THRESHOLD lines, as
21+
# clang-tidy's readability-function-size check counts them (its own count,
22+
# not a text-heuristic line counter -- a brace-counting or regex-based
23+
# stand-in cannot tell a function's extent from a `{`/`}` pair inside a
24+
# string literal or a designated initializer, both common in this codebase's
25+
# std::format calls and manifest structs; see .agents/docs/
26+
# 2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md §8).
27+
#
28+
# WHAT THIS NEEDS
29+
#
30+
# A compile database that names BMIs explicitly (-fmodule-file=...), which
31+
# only a build actually produces: `mcpp build --toolchain llvm@22.1.8` writes
32+
# compile_commands.json at the project root. This script does not build it:
33+
# the caller runs that build first. check_file_lengths.sh needs no such
34+
# division because it reads the tree.
35+
#
36+
# NOT IN CI YET. The only CI job that builds mcpp with clang (ci-linux.yml,
37+
# "toolchain: musl + llvm", llvm@20.1.7) does not produce a complete build:
38+
# libc++ 20's `std` module does not make directory_iterator's comparison
39+
# visible, and that step reads the resolution line rather than the build's
40+
# exit status. Over the partial database clang-tidy crashes. The gate is wired
41+
# in once a CI job builds mcpp with clang (mcpp-community/mcpp#729); until
42+
# then it is run by hand after `mcpp build --toolchain llvm@22.1.8`.
43+
#
44+
# clang-tidy itself is not part of the plain xim:llvm payload mcpp resolves
45+
# for `--toolchain llvm@...` (measured: xim-x-llvm/22.1.8/bin has clang,
46+
# clang-scan-deps and the LLVM binutils, no clang-tidy). It ships in the
47+
# sibling package `xim:llvm-tools` at the same version -- resolved and
48+
# searched for under the xlings package store; install it with
49+
# `xlings install xim:llvm-tools@<version that matches your llvm toolchain>`
50+
# if this script cannot find it.
51+
#
52+
# Usage: bash .github/tools/check_function_sizes.sh [repo_dir]
53+
54+
set -uo pipefail
55+
56+
REPO_DIR="${1:-$(pwd)}"
57+
cd "$REPO_DIR" || { echo "FAIL: cannot cd to $REPO_DIR" >&2; exit 1; }
58+
59+
LINE_THRESHOLD=400
60+
DIR="src/build/prepare"
61+
PRIMARY="src/build/prepare.cppm"
62+
CDB="compile_commands.json"
63+
64+
[ -d "$DIR" ] || { echo "FAIL: $DIR does not exist -- this guard has gone stale" >&2; exit 1; }
65+
66+
if [ ! -f "$CDB" ]; then
67+
cat >&2 <<EOF
68+
FAIL: $CDB does not exist.
69+
This check reads clang-tidy's own function boundaries, which needs a
70+
compile database that names every imported module's BMI explicitly.
71+
Produce one first:
72+
mcpp build --toolchain llvm@22.1.8
73+
(any installed LLVM row works; the database is written at the project
74+
root regardless of the row's exact version).
75+
EOF
76+
exit 1
77+
fi
78+
79+
# Locate clang-tidy. It is not in the plain xim:llvm payload (see the header
80+
# comment); it is the sibling xim:llvm-tools payload, and it must be the
81+
# version of the clang that wrote compile_commands.json, because it reads the
82+
# BMIs that clang wrote. `CLANG_TIDY` may name it explicitly; otherwise
83+
# the version is read from the compiler path the database names
84+
# (`.../xim-x-llvm/<version>/bin/clang++`) and looked up in either xlings store.
85+
cdb_llvm_version() {
86+
grep -o 'xim-x-llvm/[0-9][0-9.]*/bin/clang' "$CDB" 2>/dev/null | head -1 \
87+
| sed 's|xim-x-llvm/\([0-9.]*\)/bin/clang|\1|'
88+
}
89+
find_clang_tidy() { # $1 = the llvm version
90+
local root
91+
for root in "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs" "$HOME/.xlings/data/xpkgs"; do
92+
[ -x "$root/xim-x-llvm-tools/$1/bin/clang-tidy" ] \
93+
&& { echo "$root/xim-x-llvm-tools/$1/bin/clang-tidy"; return 0; }
94+
done
95+
return 1
96+
}
97+
98+
if [ -n "${CLANG_TIDY:-}" ]; then
99+
[ -x "$CLANG_TIDY" ] || { echo "FAIL: CLANG_TIDY=$CLANG_TIDY is not executable" >&2; exit 1; }
100+
else
101+
LLVM_VERSION="$(cdb_llvm_version)"
102+
CLANG_TIDY="$( [ -n "$LLVM_VERSION" ] && find_clang_tidy "$LLVM_VERSION" )" || {
103+
cat >&2 <<EOF
104+
FAIL: no clang-tidy of the llvm version that wrote $CDB (${LLVM_VERSION:-unknown})
105+
was found under an xlings package store. Install that toolchain's sibling:
106+
xlings install xim:llvm-tools@${LLVM_VERSION:-<version>}
107+
EOF
108+
exit 1
109+
}
110+
fi
111+
112+
# The files this database actually has entries for, restricted to the
113+
# decomposition's own directory (plus the primary interface, if it is ever
114+
# given its own compiled entry point -- it has none today, since it defines
115+
# only declarations and inline exports; the loop below tolerates that).
116+
mapfile -t FILES < <(python3 - "$CDB" "$DIR" "$PRIMARY" <<'PYEOF'
117+
import json, sys
118+
cdb_path, dirname, primary = sys.argv[1], sys.argv[2], sys.argv[3]
119+
with open(cdb_path) as f:
120+
entries = json.load(f)
121+
seen = set()
122+
for e in entries:
123+
path = e["file"]
124+
if f"/{dirname}/" in path or path.endswith(f"/{primary}"):
125+
seen.add(path)
126+
for p in sorted(seen):
127+
print(p)
128+
PYEOF
129+
)
130+
131+
if [ "${#FILES[@]}" -eq 0 ]; then
132+
echo "FAIL: $CDB has no entry under $DIR -- was it built with a matching source tree?" >&2
133+
exit 1
134+
fi
135+
136+
echo "checking ${#FILES[@]} file(s) with $CLANG_TIDY (LineThreshold=$LINE_THRESHOLD)..."
137+
138+
OUT="$(mktemp)"
139+
trap 'rm -f "$OUT"' EXIT
140+
141+
# No --warnings-as-errors: the only check enabled is readability-function-size
142+
# itself, and a finding in json.hpp (bundled third-party, reached through one
143+
# of these files' imports) would then make clang-tidy exit non-zero on every
144+
# run regardless of this decomposition's own state -- exactly the ambiguity
145+
# the "diagnostic tool problem" branch below exists to catch, and it cannot
146+
# tell the two apart from an exit code alone. The `relevant` filter is the
147+
# sole pass/fail signal; clang-tidy's own exit code is read only as a sign
148+
# that the tool itself failed to run (a bad compile command, a crash), which
149+
# a plain warning never produces.
150+
"$CLANG_TIDY" \
151+
--checks='-*,readability-function-size' \
152+
--config="{CheckOptions: {readability-function-size.LineThreshold: '$LINE_THRESHOLD'}}" \
153+
-p "$REPO_DIR" \
154+
"${FILES[@]}" > "$OUT" 2>&1
155+
rc=$?
156+
157+
# Only findings inside the decomposition's own directory gate the build: a
158+
# bundled third-party header (e.g. modules/libs/src/json/json.hpp) reached
159+
# through one of these files' imports is not this decomposition's to fix.
160+
# A finding is a diagnostic line, which ends with the bracketed check name; a
161+
# crash dump also names the check (in its program arguments) together with
162+
# every file path, and must not read as a finding.
163+
relevant=$(grep -E '\[readability-function-size\]$' "$OUT" | grep -F -e "/$DIR/" -e "/$(basename "$PRIMARY")" || true)
164+
165+
if [ -n "$relevant" ]; then
166+
echo "$relevant" >&2
167+
echo >&2
168+
echo "FAIL: function(s) over $LINE_THRESHOLD lines under $DIR -- see above." >&2
169+
echo " Split at the sub-section boundaries its own banners already name" >&2
170+
echo " (mcpp-community/mcpp#722's own method), the way phase13_finish," >&2
171+
echo " phase4b_graph_worklist, phase6_features_and_host_tools and" >&2
172+
echo " phase9_target_side were split." >&2
173+
exit 1
174+
fi
175+
176+
if [ "$rc" -ne 0 ]; then
177+
echo "FAIL: clang-tidy exited $rc with no readability-function-size finding under $DIR" >&2
178+
echo " (a diagnostic tool problem, not a function-size one -- see the log):" >&2
179+
cat "$OUT" >&2
180+
exit 1
181+
fi
182+
183+
echo "ok: no function under $DIR (or $PRIMARY) exceeds $LINE_THRESHOLD lines"
184+
exit 0

‎.github/workflows/bootstrap-macos.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# Dormant (workflow_dispatch only), but kept in step with the rest —
1818
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
1919
# index cannot resolve two packages that share a short name.
20-
XLINGS_VERSION: '2026.9.27.1'
20+
XLINGS_VERSION: '2026.9.28.1'
2121
steps:
2222
- uses: actions/checkout@v4
2323

0 commit comments

Comments
 (0)