diff --git a/cmd/mxcli/cmd_brain_test.go b/cmd/mxcli/cmd_brain_test.go index a112929967..9331df42e8 100644 --- a/cmd/mxcli/cmd_brain_test.go +++ b/cmd/mxcli/cmd_brain_test.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "time" @@ -207,3 +208,27 @@ func TestChangedShardsMapsPathsToShards(t *testing.T) { } func day() time.Time { return time.Date(2026, 9, 3, 0, 0, 0, 0, time.UTC) } + +// The store's project.md is documented as loaded every session, and its cap — +// the tightest in the store — is justified by exactly that. Routing to it +// through the skill alone does not make it true: a skill is triggered by +// symptom, so a session that never hits the symptom never reads the project's +// decisions. The generated CLAUDE.md is the only thing that makes the claim +// mechanical, which is why it is asserted here rather than left to review. +func TestGeneratedClaudeMDRoutesToTheBrainAtSessionStart(t *testing.T) { + md := generateClaudeMD("Demo", "Demo.mpr") + for _, want := range []string{ + "docs/brain/project.md", // the unconditional read + "docs/brain/modules/", // the on-demand shards + "brain plan", // how to pick work up + } { + if !strings.Contains(md, want) { + t.Errorf("generated CLAUDE.md does not mention %q — project.md is then loaded only when a symptom happens to trigger the skill, and the cap that assumes otherwise is unfounded", want) + } + } + // It has to come before the bulk of the file, or "read this first" is a + // claim the document's own ordering contradicts. + if i := strings.Index(md, "docs/brain/project.md"); i < 0 || i > len(md)/3 { + t.Errorf("the brain section is at byte %d of %d; it is meant to be read first", i, len(md)) + } +} diff --git a/cmd/mxcli/init_claudemd.go b/cmd/mxcli/init_claudemd.go index 5cce90c5f8..5acd0fe10e 100644 --- a/cmd/mxcli/init_claudemd.go +++ b/cmd/mxcli/init_claudemd.go @@ -60,6 +60,25 @@ func generateClaudeMD(projectName, mprFile string) string { w("This is a Mendix project configured for AI-assisted development using mxcli and MDL (Mendix Definition Language).\n\n") // ── Communication Style ──────────────────────────────────────── + // The brain's project.md is described as "loaded every session", and this + // is the only thing that makes that true. Routing to it through the skill + // alone does not: a skill is triggered by symptom, so a session that never + // hits the symptom never learns the project's own decisions — and the + // tightest cap in the store was justified by an unconditional load that + // nothing actually performed. + w("## Project Brain — read this first\n\n") + w("If " + bt + "docs/brain/" + bt + " exists, read " + bt + "docs/brain/project.md" + bt + " before doing anything\n") + w("else. It holds the decisions this project has already made — things no command can\n") + w("tell you, and that are cheap to contradict by accident.\n\n") + w("Then, depending on what you are doing:\n\n") + w("- **Building in a module** — also read " + bt + "docs/brain/modules/.md" + bt + " for the\n") + w(" modules you are about to touch. Not the whole directory; only those.\n") + w("- **Planning, or picking work up** — run " + bt + "./mxcli brain plan -p " + mprPath + bt + ".\n") + w(" It reports what is built from the model itself, so it cannot be out of date.\n\n") + w("Record what you learn with " + bt + "./mxcli brain capture" + bt + ". Read\n") + w(bt + ".ai-context/skills/project-brain/SKILL.md" + bt + " for what belongs there and what does not —\n") + w("the short version is that anything mxcli can answer must never be written down.\n\n") + w("## Communication Style\n\n") w("When discussing changes with the user:\n\n") w("- **Never show raw MDL scripts in chat.** Instead, describe changes in plain language as a numbered list.\n")