一键导入
new-feature
Scaffolds a full-stack feature for tutti-belli — lib functions, actions, and an optional page — following the project's established patterns
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Scaffolds a full-stack feature for tutti-belli — lib functions, actions, and an optional page — following the project's established patterns
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | new-feature |
| description | Scaffolds a full-stack feature for tutti-belli — lib functions, actions, and an optional page — following the project's established patterns |
| user-invocable | true |
You are scaffolding a new feature in the tutti-belli Astro 6 SSR app. Follow these steps exactly.
If $ARGUMENTS is provided, treat it as the feature name/description and proceed.
Otherwise, ask the user: "What feature are you building? Give a short name (e.g. announcements, song-requests) and describe what it does."
From the feature name, derive:
<name> — a lowercase, hyphen-separated identifier (e.g. song-requests)<camelName> — camelCase version (e.g. songRequests)<PascalName> — PascalCase version (e.g. SongRequests)Ask the user which pieces they want created:
src/lib/<name>.ts — DB query / business logic functionssrc/actions/<name>.ts — Astro Actions (form handlers)src/pages/ensembles/[id]/<name>.astro)Default is all three. Let them opt out of any piece.
Also ask: What permission level does this feature require?
member — any active ensemble memberadmin — ensemble admin onlysite-admin — site-wide admin (user.role === 'admin')src/lib/<name>.tsCreate the file. Use this structure as a template:
import { db, eq, <TableNames> } from 'astro:db';
// TODO: add query functions for <name>
export async function get<PascalName>s(ensembleId: string) {
return await db
.select()
.from(<Table>)
.where(eq(<Table>.ensembleId, ensembleId))
.all();
}
export async function create<PascalName>(/* params */) {
await db.insert(<Table>).values({
id: crypto.randomUUID(),
// fields...
});
}
Rules:
astro:db — never import astro:db in page frontmattercrypto.randomUUID() for all primary keyseq, and, or, desc, asc from astro:db for query operatorssrc/actions/<name>.tsCreate the file. Use this structure as a template:
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro/zod';
import { assertEnsembleAdmin } from './utils'; // only if admin-gated
import { create<PascalName>, update<PascalName>, delete<PascalName> } from '@lib/<name>';
export const <camelName> = {
create: defineAction({
accept: 'form',
input: z.object({
ensembleId: z.string(),
// feature-specific fields...
}),
handler: async (input, context) => {
const user = context.locals.user;
if (!user) throw new ActionError({ code: 'UNAUTHORIZED' });
// permission check:
await assertEnsembleAdmin(input.ensembleId, user); // if admin
// or: await assertEnsembleMember(input.ensembleId, user); // if member
await create<PascalName>(/* args */);
},
}),
// add update/delete actions as appropriate
};
Rules:
context.locals.user first and throw UNAUTHORIZED if missingassertEnsembleAdmin from ./utils for ensemble admin checksActionError (from astro:actions) for all error cases — never return error objects@lib/<name> functionsaccept: 'form' for all actions (HTML form POSTs)Then add the export to src/actions/index.ts:
import { <camelName> } from './<name>';<camelName> to the server export objectCreate the .astro file at the requested path. Use this structure:
---
import { actions } from 'astro:actions';
import { getEnsembleById } from '@lib/ensemble'; // adjust as needed
import Layout from '@layouts/BaseLayout.astro'; // or SingleColumnLayout for simple pages
// import any needed lib functions from @lib/<name>
const { id } = Astro.params;
const user = Astro.locals.user;
if (!user) return Astro.redirect('/login');
const ensemble = await getEnsembleById(id!);
if (!ensemble) return Astro.redirect('/');
// Permission check (adjust for feature's permission level):
// const member = await getEnsembleMember(ensemble.id, user.id);
// if (!member) return Astro.redirect('/');
// Read action results
const createResult = Astro.getActionResult(actions.<camelName>.create);
if (createResult && !createResult.error) {
return Astro.redirect(Astro.url.pathname);
}
// Fetch data
// const items = await get<PascalName>s(ensemble.id);
---
<Layout title="<PascalName>">
<section class="section">
<div class="container">
<!-- TODO: page content -->
</div>
</section>
</Layout>
Rules:
astro:db in page frontmatter — all DB access goes through @lib/* functions@layouts/BaseLayout.astro for ensemble sub-pages, @layouts/SingleColumnLayout.astro for narrow forms@components/* imports — never raw HTML equivalents. Key components:
Box.astro instead of <div class="box">Button.astro instead of <button> or <a class="button">Icon.astro instead of <i class="fas ...">TextInput.astro instead of <input class="input">Select.astro instead of <div class="select"><select>Table.astro, Modal.astro, Notification.astro, Tag.astro as neededreturn Astro.redirect(...)) to prevent double-submit<script> DOM manipulation must be inside document.addEventListener('astro:page-load', () => { ... })Invoke the /test-feature <name> skill to write integration and e2e tests for the scaffolded feature and run them.
After all files are created and tests pass, output:
pnpm check to catch TypeScript errors