From 499412ef6f4b08d8eaa8086b6816a371847183ca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 06:48:29 +0000 Subject: [PATCH] fix(create-objectstack): the scaffolded AGENTS.md lists only the src directories the config loads Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01FEkQ5nNCtq276yJtjqKq8B --- .changeset/scaffold-agents-md-wired-dirs.md | 12 ++++ .../src/agents-project-structure.test.ts | 69 +++++++++++++++++++ .../src/templates/AGENTS.md | 15 ++-- 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 .changeset/scaffold-agents-md-wired-dirs.md create mode 100644 packages/create-objectstack/src/agents-project-structure.test.ts diff --git a/.changeset/scaffold-agents-md-wired-dirs.md b/.changeset/scaffold-agents-md-wired-dirs.md new file mode 100644 index 00000000000..e60b6107b6c --- /dev/null +++ b/.changeset/scaffold-agents-md-wired-dirs.md @@ -0,0 +1,12 @@ +--- +'create-objectstack': patch +--- + +fix(create-objectstack): the scaffolded AGENTS.md lists only the `src/` directories the project's `objectstack.config.ts` loads + +Clause-②: no +Metadata-protocol: none + +The "Project Structure" block of the `AGENTS.md` a new project gets named `src/reports/`, `src/agents/`, `src/i18n/` and `src/handlers/`, which the scaffolded `objectstack.config.ts` never imports, so metadata a coding agent wrote there was never loaded and `npm run validate` still passed. It left out `src/skills/` and `src/picklists/`, which the config does load. The block now lists exactly the eight wired directories (objects, views, actions, flows, dashboards, apps, skills, picklists) and says that a new `src/` directory is never loaded or validated until its barrel is imported in `objectstack.config.ts`. What the config loads is unchanged. + +An existing project keeps the `AGENTS.md` it was created with; to adopt the fix, edit its Project Structure block to match the directories its own `objectstack.config.ts` imports. diff --git a/packages/create-objectstack/src/agents-project-structure.test.ts b/packages/create-objectstack/src/agents-project-structure.test.ts new file mode 100644 index 00000000000..445f8dbc170 --- /dev/null +++ b/packages/create-objectstack/src/agents-project-structure.test.ts @@ -0,0 +1,69 @@ +// Copyright (c) 2026 ObjectStack contributors. Apache-2.0 license. +// +// The scaffolded AGENTS.md is the first thing a coding agent reads in a new +// project, and its "Project Structure" block tells it where each kind of +// metadata goes. A directory that `objectstack.config.ts` does not import is +// never loaded, and `objectstack validate` neither counts nor checks what it +// holds — so a block that names such a directory sends the agent's work +// somewhere it silently does nothing, and validate still passes. This pins the +// block's `src/` directories to the barrels each bundled template's config +// imports, both ways: no directory the config never loads, none it loads left +// out. + +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { TEMPLATES } from './template-registry.js'; + +const templatesDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'templates'); +const AGENTS_MD = fs.readFileSync(path.join(templatesDir, 'AGENTS.md'), 'utf8'); + +/** The `src/` directories the "Project Structure" tree names, in order. */ +function projectStructureDirs(markdown: string): string[] { + const section = markdown.split(/^## Project Structure$/m)[1]; + if (section === undefined) throw new Error('AGENTS.md has no "## Project Structure" section'); + const tree = /```\n([\s\S]*?)\n```/.exec(section)?.[1]; + if (tree === undefined) throw new Error('the Project Structure section has no fenced tree'); + const lines = tree.split('\n'); + const srcAt = lines.findIndex((l) => /[├└]── src\/\s*$/.test(l)); + if (srcAt === -1) throw new Error('the Project Structure tree has no src/ entry'); + const dirs: string[] = []; + for (const line of lines.slice(srcAt + 1)) { + const m = /^│\s+[├└]── ([a-z0-9_-]+)\/\s/.exec(line); + if (!m) break; + dirs.push(m[1]); + } + return dirs; +} + +/** The `./src/` barrels a template's config imports, in order. */ +function configBarrels(configSource: string): string[] { + return [...configSource.matchAll(/^import \* as \w+ from '\.\/src\/([a-z0-9_-]+)(?:\/index(?:\.js|\.ts)?)?';$/gm)].map((m) => m[1]); +} + +const bundled = Object.entries(TEMPLATES).flatMap(([name, t]) => { + const config = path.join(templatesDir, t.source.dir, 'objectstack.config.ts'); + return fs.existsSync(config) ? [{ name, config }] : []; +}); + +describe('the scaffolded AGENTS.md Project Structure names exactly the directories the config loads', () => { + it('reads at least one bundled template with a config (the pin is not vacuous)', () => { + expect(bundled.length).toBeGreaterThan(0); + expect(projectStructureDirs(AGENTS_MD).length).toBeGreaterThan(0); + }); + + for (const { name, config } of bundled) { + it(`template "${name}": the block's src/ directories are its config's barrel imports`, () => { + const barrels = configBarrels(fs.readFileSync(config, 'utf8')); + expect(barrels.length, `no ./src barrel import read from ${config}`).toBeGreaterThan(0); + expect([...projectStructureDirs(AGENTS_MD)].sort()).toEqual([...barrels].sort()); + }); + } + + it('says a directory must be wired in objectstack.config.ts before it is loaded or validated', () => { + const section = AGENTS_MD.split(/^## Project Structure$/m)[1].split(/^## /m)[0]; + expect(section).toContain('objectstack.config.ts'); + expect(section).toMatch(/never loaded/); + }); +}); diff --git a/packages/create-objectstack/src/templates/AGENTS.md b/packages/create-objectstack/src/templates/AGENTS.md index f9be8f56ab7..8f0801f4fac 100644 --- a/packages/create-objectstack/src/templates/AGENTS.md +++ b/packages/create-objectstack/src/templates/AGENTS.md @@ -65,16 +65,19 @@ Run `npm run build` when you need the compiled `dist/objectstack.json` artifact. ├── src/ │ ├── objects/ # Business object definitions (snake_case names) │ ├── views/ # UI view definitions (list, form, kanban, calendar) -│ ├── apps/ # App navigation & page structure -│ ├── flows/ # Automation flows & workflows │ ├── actions/ # Custom actions (buttons, bulk ops) +│ ├── flows/ # Automation flows & workflows │ ├── dashboards/ # BI dashboards -│ ├── reports/ # Analytics reports -│ ├── agents/ # AI agent definitions -│ ├── i18n/ # Translation bundles -│ └── handlers/ # Runtime hook handlers +│ ├── apps/ # App navigation & page structure +│ ├── skills/ # AI skills +│ └── picklists/ # Shared option sets that select fields reuse ``` +These are exactly the directories `objectstack.config.ts` wires. A new directory +under `src/` is never loaded, and `npm run validate` neither counts nor checks +what it holds, until its `index.ts` barrel is imported in `objectstack.config.ts` +and handed to its stack key there. + ## AI Skills This project uses the ObjectStack skills catalog from the `@objectstack/skills`