From 2e531cfc180b70a41314b8a39af5d1cb0a547090 Mon Sep 17 00:00:00 2001 From: "d3mlabs-ai-flow[bot]" <305891656+d3mlabs-ai-flow[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:50:15 -0400 Subject: [PATCH] ai-flow /build: capture learnings from the build pass Co-authored-by: JPDuchesne <2636122+JPDuchesne@users.noreply.github.com> --- .cursor/rules/learnings-index.mdc | 9 +++++ .../learnings/pre-bundle-stdlib-only/SKILL.md | 38 +++++++++++++++++++ .../sig-retrofit-runtime-semantics/SKILL.md | 38 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .cursor/skills/learnings/pre-bundle-stdlib-only/SKILL.md create mode 100644 .cursor/skills/learnings/sig-retrofit-runtime-semantics/SKILL.md diff --git a/.cursor/rules/learnings-index.mdc b/.cursor/rules/learnings-index.mdc index 6ff67a0..327e72c 100644 --- a/.cursor/rules/learnings-index.mdc +++ b/.cursor/rules/learnings-index.mdc @@ -54,9 +54,18 @@ propose a retirement, a consolidation, or a glob-scoped sub-index split. normalization gate: it must recognize every layout Cursor writes, since render re-serializes its output and any mis-split compounds on the next pull. → .cursor/skills/learnings/plan-parse-normalizes/ +- [architecture/pre-bundle-stdlib-only] The deps bootstrap chain + (dependencies.rb → dev/deps closure, ensure_bundler) loads before the + bundle exists: stdlib-only, so no sorbet-runtime sigs — it caps below + `typed: strict`. → .cursor/skills/learnings/pre-bundle-stdlib-only/ ## toolchain +- [toolchain/sig-retrofit-runtime-semantics] Adding sigs to existing + methods changes runtime behavior (void sentinel, param validation, + private-receiver T.unsafe(self) calls raising), not just static checks. + → .cursor/skills/learnings/sig-retrofit-runtime-semantics/ + ## org tier diff --git a/.cursor/skills/learnings/pre-bundle-stdlib-only/SKILL.md b/.cursor/skills/learnings/pre-bundle-stdlib-only/SKILL.md new file mode 100644 index 0000000..68e00fb --- /dev/null +++ b/.cursor/skills/learnings/pre-bundle-stdlib-only/SKILL.md @@ -0,0 +1,38 @@ +--- +name: pre-bundle-stdlib-only +description: >- + MUST be used when adding requires, Sorbet sigs, or new files to the deps + bootstrap chain (dependencies.rb, lib/dev/deps.rb and its require_relative + closure, lib/ensure_bundler.rb), or when raising a lib/ file's typed sigil. +--- + +# The pre-bundle bootstrap chain is stdlib-only — no sigs, no T.* + +bin/setup.rb and bin/test.rb load `dependencies.rb` (→ `dev/deps` → config, +dsl, dependency_declaration, tap, cli_ui, lockfile, dependency, fetcher, +dependency_installer) and `ensure_bundler` BEFORE the bundle exists, so on a +fresh machine no gem — including sorbet-runtime — is loadable there. These +files cap at `typed: true`/`typed: false` (see the Sorbet/StrictSigil +exclusion list in .rubocop.yml): a `sig`, `extend T::Sig`, or `T.unsafe` +anywhere in the chain crashes first-time setup with a NameError on `T`. + +Wrong: + +```ruby +# lib/dev/deps/config.rb — pre-bundle file +require "sorbet-runtime" # not installed yet on a fresh machine +extend T::Sig +``` + +Right: + +```ruby +# lib/dev/deps/config.rb stays sig-free at `# typed: true`; +# files loaded only through the dev runtime (src/dev.rb requires +# sorbet-runtime first) may go `# typed: strict` with sigs. +``` + +learned-from: the typed-sigil enforcement pass (dev#139) — bringing lib/ to +`typed: strict` stopped at the bootstrap chain, whose stdlib-only comment in +dependencies.rb had not spelled out the Sorbet consequence. +date: 2026-08-28 diff --git a/.cursor/skills/learnings/sig-retrofit-runtime-semantics/SKILL.md b/.cursor/skills/learnings/sig-retrofit-runtime-semantics/SKILL.md new file mode 100644 index 0000000..874832e --- /dev/null +++ b/.cursor/skills/learnings/sig-retrofit-runtime-semantics/SKILL.md @@ -0,0 +1,38 @@ +--- +name: sig-retrofit-runtime-semantics +description: >- + MUST be used when adding Sorbet sigs to existing Ruby methods: sig-wrapped + methods behave differently at runtime (void sentinel, validated params, + private-call receivers), so retrofitting is not behavior-neutral. +--- + +# Retrofitting sigs changes runtime behavior, not just static checking + +sorbet-runtime wraps every sig'd method, so adding sigs to working code can +break it three ways: `.void` replaces the method's return value with a VOID +sentinel (callers or tests asserting `.nil?` fail); params are validated on +every call (duck-typed test fakes and StringIO-for-IO injections raise +TypeError — type such seams `T.untyped` / `T.any(IO, StringIO)`); and +`T.unsafe(self).some_method(...)` is an explicit-receiver call, which raises +NoMethodError when the target (e.g. `Kernel#system`) is private. For +runtime-sized splats keep the call receiverless and unsafe-cast the argv. + +Wrong: + +```ruby +success = T.unsafe(self).system(env, *build_command(spec), chdir: dir) +# => NoMethodError: private method 'system' called for an instance of … +``` + +Right: + +```ruby +argv = [env, *build_command(spec)] +success = system(*T.unsafe(argv), chdir: dir) +``` + +learned-from: the typed-sigil enforcement pass (dev#139) — four test +failures after lib/ went strict: two private-receiver T.unsafe(self) calls, +a `.nil?` assertion on a newly-void install_all, and a String passed where +the sig'd base contract validates a Hash. +date: 2026-08-28