From 8bfed0f2d5bd6540c20f7f27fc64fe5dd9f90c2d Mon Sep 17 00:00:00 2001 From: "d3mlabs-ai-flow[bot]" <305891656+d3mlabs-ai-flow[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:52:18 -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 | 8 +++- .../architecture/command-dispatch/SKILL.md | 7 ++-- .../skills/architecture/module-map/SKILL.md | 5 ++- .../data-define-nested-constants/SKILL.md | 38 +++++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 .cursor/skills/learnings/data-define-nested-constants/SKILL.md diff --git a/.cursor/rules/learnings-index.mdc b/.cursor/rules/learnings-index.mdc index 6ff67a0..615da41 100644 --- a/.cursor/rules/learnings-index.mdc +++ b/.cursor/rules/learnings-index.mdc @@ -33,7 +33,7 @@ propose a retirement, a consolidation, or a glob-scoped sub-index split. ## architecture -- [architecture/command-dispatch] Global builtins (cd, plan, cred, +- [architecture/command-dispatch] Global builtins (cd, clone, plan, cred, learnings) dispatch before the dev.yml gate; project commands are yaml-declared and run through Runner. → .cursor/skills/architecture/command-dispatch/ @@ -57,6 +57,12 @@ propose a retirement, a consolidation, or a glob-scoped sub-index split. ## toolchain +- [toolchain/data-define-nested-constants] Constants declared inside a + `Data.define do … end` block land on the enclosing module, and Sorbet + rejects `class X < Data.define`; value classes needing nested constants + (typed errors) are plain classes. + → .cursor/skills/learnings/data-define-nested-constants/ + ## org tier diff --git a/.cursor/skills/architecture/command-dispatch/SKILL.md b/.cursor/skills/architecture/command-dispatch/SKILL.md index 5f6dfb2..e4a527d 100644 --- a/.cursor/skills/architecture/command-dispatch/SKILL.md +++ b/.cursor/skills/architecture/command-dispatch/SKILL.md @@ -12,8 +12,9 @@ routes argv through two layers: 1. **Global builtins** — `Dev::GlobalDispatch` (`src/dev/global_dispatch.rb`) runs first, before any dev.yml lookup, - so `cd`, `plan`, `cred`, and `learnings` work from any directory. Each - owns host- or workspace-global state, never project config. + so `cd`, `clone`, `plan`, `cred`, and `learnings` work from any + directory. Each owns host- or workspace-global state, never project + config. 2. **Project commands** — everything else builds `Dev::Runner` (`src/dev/runner.rb`), which requires a dev.yml in the cwd's ancestry (`DevYamlNotFoundError` at the CLI boundary) and runs the @@ -25,7 +26,7 @@ The seams: - A new global command joins `GlobalDispatch::GLOBAL_COMMANDS` and gets a feature module under `lib/dev//` whose `Accessor` is its only CLI surface (usage, arg parsing, clean failures) — see `Cd::Accessor`, - `Plan::Accessor`, `Learnings::Accessor`. + `Clone::Accessor`, `Plan::Accessor`, `Learnings::Accessor`. - Project commands are declared in each repo's dev.yml, never hardcoded in dev's core. - Workspace-global commands resolve their root as nearest dev.yml, else diff --git a/.cursor/skills/architecture/module-map/SKILL.md b/.cursor/skills/architecture/module-map/SKILL.md index 4bdea73..3951cc6 100644 --- a/.cursor/skills/architecture/module-map/SKILL.md +++ b/.cursor/skills/architecture/module-map/SKILL.md @@ -11,8 +11,9 @@ description: >- parser/registry, dev.yml config parsing, CLI UI, GlobalDispatch. Owns dispatch and execution, no feature logic. - **`lib/dev/cd/`** — checkout jumping: RepoDiscovery walks the workspace - root, Matcher ranks, ShellHook owns the RC function (a child process - cannot cd its parent shell). + root, Matcher ranks, HookInstaller owns the RC wrapper function (a child + process cannot cd its parent shell; it also serves `dev clone`). +- **`lib/dev/clone/`** — checkout creation: RepoSpec parses `[org/]repo`, GhCloner clones via gh auth to the canonical `$DEV_CD_ROOT` path. - **`lib/dev/plan/`** — Cursor plans ⇄ GitHub issues sync (the issue is canonical; a stored merge base guards against clobbering remote edits). - **`lib/dev/deps/`** — dependency management. Layering is canonical in diff --git a/.cursor/skills/learnings/data-define-nested-constants/SKILL.md b/.cursor/skills/learnings/data-define-nested-constants/SKILL.md new file mode 100644 index 0000000..dbd9693 --- /dev/null +++ b/.cursor/skills/learnings/data-define-nested-constants/SKILL.md @@ -0,0 +1,38 @@ +--- +name: data-define-nested-constants +description: >- + MUST be used when a Data.define value class needs nested constants — a + typed error, a default, a pattern — or when a NameError reports such a + constant missing on the value class. +--- + +# Data.define blocks don't nest constants; Sorbet rejects the class form + +Constants (including `class Foo < RuntimeError`) declared inside a +`Data.define do … end` block use Ruby's *lexical* scoping, so they land +on the enclosing module, not the value class — `Value::Error` then +raises NameError while `EnclosingModule::Error` silently exists. The +escape hatch `class Value < Data.define(...)` nests correctly but fails +Sorbet's srb tc ("Superclasses must only contain constant literals", +error 4002) even in typed: false files. A value class that needs nested +constants (this repo's typed-errors rule nests errors in the raising +class) is written as a plain class with attr_readers; keep `Data.define` +only for constant-free values like `Dev::Cd::Repo`. + +Wrong — the error lands on Dev::Clone, not RepoSpec: + + RepoSpec = Data.define(:org, :name) do + class MalformedRepoError < RuntimeError; end # Dev::Clone::…! + end + +Right (dev#101): + + class RepoSpec + class MalformedRepoError < RuntimeError; end + attr_reader :org, :name + # parse factory + initialize(org:, name:) + end + +learned-from: dev#101 build pass (RepoSpec's typed error raised +NameError under test; the `< Data.define` fix then failed srb tc) +date: 2026-08-15