Skip to content

Commit 0a46a26

Browse files
fix(create-objectstack): the scaffolded AGENTS.md lists only the src directories the config loads (#29091)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XL51wryBuAmngcxc4MhBYSV)_ Fixes #28954 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:脚手架生成的 `AGENTS.md` 里「Project Structure」目录树,现在只列 `objectstack.config.ts` 真正加载的 8 个目录(objects、views、actions、flows、dashboards、apps、skills、picklists),去掉了从不加载的 `reports/`、`agents/`、`i18n/`、`handlers/`,并加一句:新目录必须在 config 里接线,否则既不加载也不被 validate 检查。另加一条包内测试,把这棵目录树和模板 config 的 barrel 导入对齐。 - **为什么改**:按「Build with Claude Code」路径的新手,coding agent 先读 AGENTS.md,再照着目录树写元数据;写进那四个死目录的东西不会加载,`npm run validate` 还照样通过,用户以为是 AI 写坏了。 - **风险与回滚**:极低。只改模板里的说明文字和一条测试,config 加载什么完全不变;已生成的项目不受影响。回滚即撤销本 PR。 - **先看**:`packages/create-objectstack/src/templates/AGENTS.md`(Project Structure 一节);`packages/create-objectstack/src/agents-project-structure.test.ts`(新增的对齐测试)。 - **两分钟验证**:`pnpm --filter create-objectstack exec vitest run src/agents-project-structure.test.ts`,预期 3/3 通过(main 上 2 条红:多出 agents/handlers/i18n/reports,缺 picklists/skills)。 - **需要你做的**:无,席位落地。 ## Why A newcomer on the "Build with Claude Code" path has a coding agent read `AGENTS.md` before it writes any metadata. The block told the agent to put reports in `src/reports/`, AI agents in `src/agents/`, translations in `src/i18n/` and hooks in `src/handlers/`. The scaffolded `objectstack.config.ts` imports none of those, so what the agent wrote there was never loaded, and `npm run validate` neither counted nor checked it (the card's repro: an invalid report there, `✓ Validation passed`). ## What changes - **`packages/create-objectstack/src/templates/AGENTS.md`, the Project Structure block.** The `src/` tree now lists exactly the eight directories the config wires, in the config's order: `objects/`, `views/`, `actions/`, `flows/`, `dashboards/`, `apps/`, `skills/`, `picklists/`. `reports/`, `agents/`, `i18n/` and `handlers/` are gone. Below the tree, one paragraph: these are exactly the directories `objectstack.config.ts` wires, and a new `src/` directory 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 (the config's own comment says the same). - **New `packages/create-objectstack/src/agents-project-structure.test.ts`.** For every bundled template in `TEMPLATES` that has an `objectstack.config.ts`, the block's `src/` directories equal the config's `import * as X from './src/X'` barrels, as sets, both ways. A third case pins that the section names `objectstack.config.ts` and says "never loaded". A package test, not a gate script. - **What any config loads is unchanged.** No template file other than `AGENTS.md` is touched. - **Changeset** `.changeset/scaffold-agents-md-wired-dirs.md`: `patch` on `create-objectstack` (the templates ship inside its `dist`), with both declaration lines and the one-line adoption note for an existing project. **Step 1 measurement (the order's ruling 2).** `TEMPLATES` (`src/template-registry.ts`) has one bundled template, `blank`. All templates share one `AGENTS.md` (`src/templates/AGENTS.md`, rendered by `index.ts` into every project); no template carries its own. `blank/objectstack.config.ts` imports eight barrels: objects, views, actions, flows, dashboards, apps, skills, picklists. The old block named ten `src/` directories: six of those eight, plus `reports/`, `agents/`, `i18n/`, `handlers/`; `skills/` and `picklists/` were missing. `blank/README.md` (its Layout section) already names exactly the eight, so it is unchanged. ## Tests - **Red on base `ee7518f9`** (`AGENTS.md` checked out from base, the new test kept): 2 failed, 1 passed. The set case received `agents`, `handlers`, `i18n`, `reports` and lacked `picklists`, `skills`; the wiring-sentence case failed. Restored: `git status --porcelain` empty, blob `8f0801f4fac0` equal to HEAD. - **Green on head:** 3/3. <details><summary>Ablation (one-off, from committed HEAD)</summary> `node scripts/ablation-replace.mjs` inserted `│ ├── reports/ # ABLATED-28954 Analytics reports` back into the tree (anchor `# BI dashboards` + newline + the `apps/` line, 1 → 0; blob `8f0801f4fac0` → `fad5dd511c4f`; the marker counted once on disk). The test: 1 failed, 2 passed, the set case receiving `+ "reports"`. The tool restored the file and proved it: blob equal to HEAD `8f0801f4fac0`, `git diff HEAD` empty; `git status --porcelain` empty after. </details> ## Acceptance notes - **Wiring the four directories instead.** Not done, by ruling: what the config loads stays as it is. Reports, agents, translations and hooks each have their own stack key, but whether a starter should wire them is a product choice for its own card; this report names none. - **Existing projects.** An already-scaffolded project keeps its `AGENTS.md`. The changeset says how to adopt the fix by hand. ## Verification <details><summary>Package tests and typecheck</summary> All readings at head `499412ef` (base `ee7518f9`), after `pnpm install --frozen-lockfile` and `pnpm --filter 'create-objectstack...' build`. - `pnpm --filter create-objectstack test`: 20 files, 304 tests passed (the new file included). Lock verdict `command-exit 0`. - `pnpm --filter create-objectstack typecheck` (`tsc --noEmit`): exit 0; `--listFiles` counts the new test file once, so the typecheck covers it. - `eslint --no-inline-config packages/create-objectstack`: exit 0. - Not done: a scaffold-and-validate run from the local package (the order called it welcome, not required). </details> <details><summary>Derived gates</summary> - `node scripts/pm/dispatch-gates.mjs --commands` at `499412ef`: 59 commands. The lane was wound down mid-run (the maintainer's call to wrap up), so the record is partial: 55 ran, 4 NOT MEASURED because the run was stopped before them (`check:type-check-debt`, `check:undeclared-dep-imports`, `check:watch-hint-literal`, `check:where-matcher`). CI runs all four. - Of the 55: 53 exit 0. `check:dual-build-cjs-loads` exit 3, PREREQUISITE NOT MET (needs a full repo build), NOT MEASURED, left to CI. `check:lean-entry-closure` exit 3 in the loop (objectql `dist` missing); after `pnpm --filter '@objectstack/objectql...' build` it answered exit 0 ("Admitted set held exactly (15 packages)"). - Not run locally, as the os-dev scope says: `test:repo`, the repo-wide lint, and the families outside the derivation. The PR's CI check-runs carry them. - No dogfood battery: the title has no `!`, and `Clause-②` has no `(narrowing)` arm. </details> ## Human Minimum None: the seat reviews and lands it. --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5nNCtq276yJtjqKq8B)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 87d6bae commit 0a46a26

3 files changed

Lines changed: 90 additions & 6 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'create-objectstack': patch
3+
---
4+
5+
fix(create-objectstack): the scaffolded AGENTS.md lists only the `src/` directories the project's `objectstack.config.ts` loads
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
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.
11+
12+
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.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) 2026 ObjectStack contributors. Apache-2.0 license.
2+
//
3+
// The scaffolded AGENTS.md is the first thing a coding agent reads in a new
4+
// project, and its "Project Structure" block tells it where each kind of
5+
// metadata goes. A directory that `objectstack.config.ts` does not import is
6+
// never loaded, and `objectstack validate` neither counts nor checks what it
7+
// holds — so a block that names such a directory sends the agent's work
8+
// somewhere it silently does nothing, and validate still passes. This pins the
9+
// block's `src/` directories to the barrels each bundled template's config
10+
// imports, both ways: no directory the config never loads, none it loads left
11+
// out.
12+
13+
import { describe, it, expect } from 'vitest';
14+
import fs from 'node:fs';
15+
import path from 'node:path';
16+
import { fileURLToPath } from 'node:url';
17+
import { TEMPLATES } from './template-registry.js';
18+
19+
const templatesDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'templates');
20+
const AGENTS_MD = fs.readFileSync(path.join(templatesDir, 'AGENTS.md'), 'utf8');
21+
22+
/** The `src/` directories the "Project Structure" tree names, in order. */
23+
function projectStructureDirs(markdown: string): string[] {
24+
const section = markdown.split(/^## Project Structure$/m)[1];
25+
if (section === undefined) throw new Error('AGENTS.md has no "## Project Structure" section');
26+
const tree = /```\n([\s\S]*?)\n```/.exec(section)?.[1];
27+
if (tree === undefined) throw new Error('the Project Structure section has no fenced tree');
28+
const lines = tree.split('\n');
29+
const srcAt = lines.findIndex((l) => /[├└]── src\/\s*$/.test(l));
30+
if (srcAt === -1) throw new Error('the Project Structure tree has no src/ entry');
31+
const dirs: string[] = [];
32+
for (const line of lines.slice(srcAt + 1)) {
33+
const m = /^│\s+[├└]── ([a-z0-9_-]+)\/\s/.exec(line);
34+
if (!m) break;
35+
dirs.push(m[1]);
36+
}
37+
return dirs;
38+
}
39+
40+
/** The `./src/<dir>` barrels a template's config imports, in order. */
41+
function configBarrels(configSource: string): string[] {
42+
return [...configSource.matchAll(/^import \* as \w+ from '\.\/src\/([a-z0-9_-]+)(?:\/index(?:\.js|\.ts)?)?';$/gm)].map((m) => m[1]);
43+
}
44+
45+
const bundled = Object.entries(TEMPLATES).flatMap(([name, t]) => {
46+
const config = path.join(templatesDir, t.source.dir, 'objectstack.config.ts');
47+
return fs.existsSync(config) ? [{ name, config }] : [];
48+
});
49+
50+
describe('the scaffolded AGENTS.md Project Structure names exactly the directories the config loads', () => {
51+
it('reads at least one bundled template with a config (the pin is not vacuous)', () => {
52+
expect(bundled.length).toBeGreaterThan(0);
53+
expect(projectStructureDirs(AGENTS_MD).length).toBeGreaterThan(0);
54+
});
55+
56+
for (const { name, config } of bundled) {
57+
it(`template "${name}": the block's src/ directories are its config's barrel imports`, () => {
58+
const barrels = configBarrels(fs.readFileSync(config, 'utf8'));
59+
expect(barrels.length, `no ./src barrel import read from ${config}`).toBeGreaterThan(0);
60+
expect([...projectStructureDirs(AGENTS_MD)].sort()).toEqual([...barrels].sort());
61+
});
62+
}
63+
64+
it('says a directory must be wired in objectstack.config.ts before it is loaded or validated', () => {
65+
const section = AGENTS_MD.split(/^## Project Structure$/m)[1].split(/^## /m)[0];
66+
expect(section).toContain('objectstack.config.ts');
67+
expect(section).toMatch(/never loaded/);
68+
});
69+
});

‎packages/create-objectstack/src/templates/AGENTS.md‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,19 @@ Run `npm run build` when you need the compiled `dist/objectstack.json` artifact.
6565
├── src/
6666
│ ├── objects/ # Business object definitions (snake_case names)
6767
│ ├── views/ # UI view definitions (list, form, kanban, calendar)
68-
│ ├── apps/ # App navigation & page structure
69-
│ ├── flows/ # Automation flows & workflows
7068
│ ├── actions/ # Custom actions (buttons, bulk ops)
69+
│ ├── flows/ # Automation flows & workflows
7170
│ ├── dashboards/ # BI dashboards
72-
│ ├── reports/ # Analytics reports
73-
│ ├── agents/ # AI agent definitions
74-
│ ├── i18n/ # Translation bundles
75-
│ └── handlers/ # Runtime hook handlers
71+
│ ├── apps/ # App navigation & page structure
72+
│ ├── skills/ # AI skills
73+
│ └── picklists/ # Shared option sets that select fields reuse
7674
```
7775

76+
These are exactly the directories `objectstack.config.ts` wires. A new directory
77+
under `src/` is never loaded, and `npm run validate` neither counts nor checks
78+
what it holds, until its `index.ts` barrel is imported in `objectstack.config.ts`
79+
and handed to its stack key there.
80+
7881
## AI Skills
7982

8083
This project uses the ObjectStack skills catalog from the `@objectstack/skills`

0 commit comments

Comments
 (0)