@baldurpan/create-ai-workflow 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/agents-block.js +70 -0
- package/dist/bin.js +14 -0
- package/dist/check/markdown.js +47 -0
- package/dist/check/parse.js +113 -0
- package/dist/check/rules.js +245 -0
- package/dist/cli.js +109 -0
- package/dist/commands/check.js +41 -0
- package/dist/commands/install.js +76 -0
- package/dist/commands/standards-add.js +145 -0
- package/dist/commands/update.js +214 -0
- package/dist/layout.js +115 -0
- package/dist/log.js +20 -0
- package/dist/manifest.js +53 -0
- package/dist/paths.js +41 -0
- package/package.json +37 -0
- package/templates/blocks/agents-block.md +30 -0
- package/templates/claude/agents/planner.agent.md +60 -0
- package/templates/claude/agents/reviewer.agent.md +77 -0
- package/templates/context/README.md +81 -0
- package/templates/context/plan-template.md +69 -0
- package/templates/context/plan-template.notes.md +55 -0
- package/templates/context/roles/coder.md +76 -0
- package/templates/context/workflow.md +143 -0
- package/templates/skills/feature-close/SKILL.md +102 -0
- package/templates/skills/feature-implement/SKILL.md +142 -0
- package/templates/skills/feature-plan/SKILL.md +138 -0
- package/templates/skills/feature-status/SKILL.md +86 -0
- package/templates/skills/onboard/SKILL.md +122 -0
- package/templates/skills/orchestrate/SKILL.md +92 -0
- package/templates/skills/roadmap/SKILL.md +97 -0
- package/templates/standards/.source +8 -0
- package/templates/standards/README.md +104 -0
- package/templates/standards/architecture/api-design.md +212 -0
- package/templates/standards/architecture/dependency-boundaries.md +104 -0
- package/templates/standards/architecture/feature-driven.md +102 -0
- package/templates/standards/architecture/folder-structure.md +108 -0
- package/templates/standards/architecture/monorepos.md +107 -0
- package/templates/standards/architecture/refactoring.md +97 -0
- package/templates/standards/architecture/shared-code.md +81 -0
- package/templates/standards/docs/PHP-SPEC.md +1195 -0
- package/templates/standards/docs/SPEC.md +540 -0
- package/templates/standards/examples/bad/data-fetching.tsx +52 -0
- package/templates/standards/examples/bad/derived-state.tsx +60 -0
- package/templates/standards/examples/bad/feature-component.tsx +80 -0
- package/templates/standards/examples/bad/prop-drilling.tsx +70 -0
- package/templates/standards/examples/good/data-fetching.tsx +53 -0
- package/templates/standards/examples/good/derived-state.tsx +52 -0
- package/templates/standards/examples/good/feature-component.tsx +141 -0
- package/templates/standards/examples/good/invoice.schema.ts +20 -0
- package/templates/standards/examples/good/prop-drilling.tsx +83 -0
- package/templates/standards/philosophy/ai-agent-behavior.md +93 -0
- package/templates/standards/philosophy/core-principles.md +49 -0
- package/templates/standards/philosophy/incremental-abstraction.md +56 -0
- package/templates/standards/philosophy/maintainability.md +66 -0
- package/templates/standards/philosophy/readability.md +87 -0
- package/templates/standards/php/README.md +62 -0
- package/templates/standards/php/anti-patterns.md +259 -0
- package/templates/standards/php/architecture.md +241 -0
- package/templates/standards/php/frameworks.md +197 -0
- package/templates/standards/php/rules.md +205 -0
- package/templates/standards/php/security.md +190 -0
- package/templates/standards/php/testing.md +139 -0
- package/templates/standards/php/tooling.md +245 -0
- package/templates/standards/react/accessibility.md +130 -0
- package/templates/standards/react/anti-patterns.md +187 -0
- package/templates/standards/react/component-design.md +139 -0
- package/templates/standards/react/error-boundaries.md +195 -0
- package/templates/standards/react/forms.md +147 -0
- package/templates/standards/react/hooks.md +88 -0
- package/templates/standards/react/memoization.md +87 -0
- package/templates/standards/react/state-management.md +123 -0
- package/templates/standards/react/testing.md +118 -0
- package/templates/standards/react/use-effect.md +110 -0
- package/templates/standards/security/api-security.md +136 -0
- package/templates/standards/security/auth.md +112 -0
- package/templates/standards/security/secrets.md +105 -0
- package/templates/standards/security/validation.md +89 -0
- package/templates/standards/templates/.editorconfig +16 -0
- package/templates/standards/templates/.nvmrc +1 -0
- package/templates/standards/templates/AGENTS.md +46 -0
- package/templates/standards/templates/CLAUDE.md +3 -0
- package/templates/standards/templates/_dot_gitignore +50 -0
- package/templates/standards/templates/biome.json +53 -0
- package/templates/standards/templates/eslint.config.js +61 -0
- package/templates/standards/templates/project-readme.md +76 -0
- package/templates/standards/templates/pull-request-template.md +46 -0
- package/templates/standards/templates/tsconfig.json +35 -0
- package/templates/standards/tooling/biome.md +92 -0
- package/templates/standards/tooling/ci.md +128 -0
- package/templates/standards/tooling/dates.md +194 -0
- package/templates/standards/tooling/dependencies.md +90 -0
- package/templates/standards/tooling/nx.md +113 -0
- package/templates/standards/tooling/observability.md +183 -0
- package/templates/standards/tooling/prisma.md +152 -0
- package/templates/standards/tooling/shadcn.md +115 -0
- package/templates/standards/tooling/tailwind.md +135 -0
- package/templates/standards/tooling/tanstack-query.md +214 -0
- package/templates/standards/tooling/tanstack-router.md +365 -0
- package/templates/standards/tooling/tanstack.md +31 -0
- package/templates/standards/tooling/vite.md +135 -0
- package/templates/standards/typescript/anti-patterns.md +107 -0
- package/templates/standards/typescript/error-handling.md +216 -0
- package/templates/standards/typescript/naming.md +78 -0
- package/templates/standards/typescript/rules.md +159 -0
- package/templates/standards/typescript/tsconfig/base.json +32 -0
- package/templates/standards/typescript/tsconfig/nextjs.json +12 -0
- package/templates/standards/typescript/tsconfig/react.json +9 -0
- package/templates/standards/typescript/validation.md +104 -0
- package/templates/stubs/executors.md +49 -0
- package/templates/stubs/findings.md +40 -0
- package/templates/stubs/history.md +13 -0
- package/templates/stubs/roadmap.md +29 -0
- package/templates/stubs/stack.md +35 -0
- package/templates/stubs/verify.md +48 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BAD: Drilling `user` through layers that never use it
|
|
3
|
+
*
|
|
4
|
+
* Problems:
|
|
5
|
+
* - Layout and Sidebar accept `user` only to forward it — they don't render it
|
|
6
|
+
* - Every intermediate signature grows when shared values are added
|
|
7
|
+
* - Refactoring any layer ripples through the whole tree
|
|
8
|
+
* - TypeScript noise: prop types repeated everywhere
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface User {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PageProps {
|
|
17
|
+
user: User;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Page({ user }: PageProps) {
|
|
21
|
+
// ✗ Layout doesn't use `user` — only forwards it
|
|
22
|
+
return <Layout user={user} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface LayoutProps {
|
|
26
|
+
user: User;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Layout({ user }: LayoutProps) {
|
|
30
|
+
return (
|
|
31
|
+
<div>
|
|
32
|
+
<Header />
|
|
33
|
+
{/* ✗ Sidebar doesn't use `user` either — just forwards */}
|
|
34
|
+
<Sidebar user={user} />
|
|
35
|
+
<Content />
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface SidebarProps {
|
|
41
|
+
user: User;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function Sidebar({ user }: SidebarProps) {
|
|
45
|
+
return (
|
|
46
|
+
<aside>
|
|
47
|
+
<Nav />
|
|
48
|
+
{/* Finally, UserMenu actually uses the prop */}
|
|
49
|
+
<UserMenu user={user} />
|
|
50
|
+
</aside>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface UserMenuProps {
|
|
55
|
+
user: User;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function UserMenu({ user }: UserMenuProps) {
|
|
59
|
+
return <button type="button">{user.name}</button>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Header() {
|
|
63
|
+
return <header />;
|
|
64
|
+
}
|
|
65
|
+
function Content() {
|
|
66
|
+
return <main />;
|
|
67
|
+
}
|
|
68
|
+
function Nav() {
|
|
69
|
+
return <nav />;
|
|
70
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GOOD: Data fetching with TanStack Query
|
|
3
|
+
*
|
|
4
|
+
* - Caches, deduplicates, and refetches automatically
|
|
5
|
+
* - Loading and error states are first-class
|
|
6
|
+
* - No race conditions
|
|
7
|
+
* - No useEffect / useState for fetch state
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useQuery } from "@tanstack/react-query";
|
|
11
|
+
|
|
12
|
+
interface User {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
email: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function fetchUser(id: string): Promise<User> {
|
|
19
|
+
const response = await fetch(`/api/users/${id}`);
|
|
20
|
+
if (!response.ok) throw new Error(`Failed to fetch user: ${response.statusText}`);
|
|
21
|
+
return response.json();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface UserProfileProps {
|
|
25
|
+
userId: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function UserProfile({ userId }: UserProfileProps) {
|
|
29
|
+
const { data: user, isLoading, error } = useQuery({
|
|
30
|
+
queryKey: ["user", userId],
|
|
31
|
+
queryFn: () => fetchUser(userId),
|
|
32
|
+
staleTime: 60_000,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (error) {
|
|
36
|
+
return (
|
|
37
|
+
<div role="alert">
|
|
38
|
+
We couldn't load this profile. Please refresh and try again.
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (isLoading || !user) {
|
|
44
|
+
return <div aria-busy="true">Loading…</div>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<article>
|
|
49
|
+
<h1>{user.name}</h1>
|
|
50
|
+
<p>{user.email}</p>
|
|
51
|
+
</article>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GOOD: Derived values computed during render
|
|
3
|
+
*
|
|
4
|
+
* - One source of truth (items + search)
|
|
5
|
+
* - No re-renders triggered by syncing state to state
|
|
6
|
+
* - Fewer bugs: filtered/total are always in sync with inputs
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
|
|
11
|
+
interface Item {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
price: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ItemListProps {
|
|
18
|
+
items: Item[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ItemList({ items }: ItemListProps) {
|
|
22
|
+
const [search, setSearch] = useState("");
|
|
23
|
+
|
|
24
|
+
// Computed during render — always in sync, no useEffect needed
|
|
25
|
+
const filtered = items.filter((item) =>
|
|
26
|
+
item.name.toLowerCase().includes(search.toLowerCase()),
|
|
27
|
+
);
|
|
28
|
+
const total = filtered.reduce((sum, item) => sum + item.price, 0);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<section>
|
|
32
|
+
<label>
|
|
33
|
+
Search
|
|
34
|
+
<input
|
|
35
|
+
type="search"
|
|
36
|
+
value={search}
|
|
37
|
+
onChange={(event) => setSearch(event.target.value)}
|
|
38
|
+
/>
|
|
39
|
+
</label>
|
|
40
|
+
<p>
|
|
41
|
+
{filtered.length} items — total ${total.toFixed(2)}
|
|
42
|
+
</p>
|
|
43
|
+
<ul>
|
|
44
|
+
{filtered.map((item) => (
|
|
45
|
+
<li key={item.id}>
|
|
46
|
+
{item.name} — ${item.price.toFixed(2)}
|
|
47
|
+
</li>
|
|
48
|
+
))}
|
|
49
|
+
</ul>
|
|
50
|
+
</section>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GOOD EXAMPLE: Feature-driven, colocated, accessible invoice table
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates:
|
|
5
|
+
* - Feature-colocated hook and types
|
|
6
|
+
* - TanStack Query for server state
|
|
7
|
+
* - URL state for filters
|
|
8
|
+
* - Accessible table markup
|
|
9
|
+
* - Explicit props, no spreading unknown props
|
|
10
|
+
* - No useEffect for data fetching or derived state
|
|
11
|
+
* - Controlled loading and error states
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useQuery } from "@tanstack/react-query";
|
|
15
|
+
import { useSearch } from "@tanstack/react-router";
|
|
16
|
+
import { type Invoice, invoiceSchema } from "./invoice.schema";
|
|
17
|
+
|
|
18
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
interface InvoiceTableProps {
|
|
21
|
+
onRowClick: (id: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Data fetching ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
async function fetchInvoices(status: string): Promise<Invoice[]> {
|
|
27
|
+
const response = await fetch(`/api/invoices?status=${status}`);
|
|
28
|
+
if (!response.ok) throw new Error(`Failed to fetch invoices: ${response.statusText}`);
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
return invoiceSchema.array().parse(data);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Feature hook ─────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function useInvoiceTable() {
|
|
36
|
+
// URL state — shareable, bookmarkable
|
|
37
|
+
const { status = "all" } = useSearch({ from: "/invoices" });
|
|
38
|
+
|
|
39
|
+
const { data: invoices = [], isLoading, error } = useQuery({
|
|
40
|
+
queryKey: ["invoices", status],
|
|
41
|
+
queryFn: () => fetchInvoices(status),
|
|
42
|
+
staleTime: 30_000,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Derived state — computed during render, no useEffect
|
|
46
|
+
const totalAmount = invoices.reduce((sum, inv) => sum + inv.amount, 0);
|
|
47
|
+
|
|
48
|
+
return { invoices, isLoading, error, totalAmount, status };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export function InvoiceTable({ onRowClick }: InvoiceTableProps) {
|
|
54
|
+
const { invoices, isLoading, error, totalAmount } = useInvoiceTable();
|
|
55
|
+
|
|
56
|
+
if (error) {
|
|
57
|
+
return (
|
|
58
|
+
<div role="alert">
|
|
59
|
+
<p>We couldn't load your invoices. Please refresh the page or contact support.</p>
|
|
60
|
+
<p>
|
|
61
|
+
<small>Error: {error.message}</small>
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isLoading) {
|
|
68
|
+
return <div aria-busy="true" aria-label="Loading invoices">Loading…</div>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (invoices.length === 0) {
|
|
72
|
+
return <p>No invoices found.</p>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<section aria-labelledby="invoice-table-heading">
|
|
77
|
+
<h2 id="invoice-table-heading">Invoices</h2>
|
|
78
|
+
<table>
|
|
79
|
+
<caption>
|
|
80
|
+
{invoices.length} invoices — total:{" "}
|
|
81
|
+
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
|
|
82
|
+
totalAmount,
|
|
83
|
+
)}
|
|
84
|
+
</caption>
|
|
85
|
+
<thead>
|
|
86
|
+
<tr>
|
|
87
|
+
<th scope="col">Title</th>
|
|
88
|
+
<th scope="col">Status</th>
|
|
89
|
+
<th scope="col">Amount</th>
|
|
90
|
+
<th scope="col">Due Date</th>
|
|
91
|
+
<th scope="col"><span className="sr-only">Actions</span></th>
|
|
92
|
+
</tr>
|
|
93
|
+
</thead>
|
|
94
|
+
<tbody>
|
|
95
|
+
{invoices.map((invoice) => (
|
|
96
|
+
<InvoiceRow
|
|
97
|
+
key={invoice.id}
|
|
98
|
+
invoice={invoice}
|
|
99
|
+
onRowClick={onRowClick}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
104
|
+
</section>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Sub-component ─────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
interface InvoiceRowProps {
|
|
111
|
+
invoice: Invoice;
|
|
112
|
+
onRowClick: (id: string) => void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function InvoiceRow({ invoice, onRowClick }: InvoiceRowProps) {
|
|
116
|
+
return (
|
|
117
|
+
<tr>
|
|
118
|
+
<td>{invoice.title}</td>
|
|
119
|
+
<td>{invoice.status}</td>
|
|
120
|
+
<td>
|
|
121
|
+
{new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(
|
|
122
|
+
invoice.amount,
|
|
123
|
+
)}
|
|
124
|
+
</td>
|
|
125
|
+
<td>
|
|
126
|
+
<time dateTime={invoice.dueDate.toISOString()}>
|
|
127
|
+
{invoice.dueDate.toLocaleDateString()}
|
|
128
|
+
</time>
|
|
129
|
+
</td>
|
|
130
|
+
<td>
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={() => onRowClick(invoice.id)}
|
|
134
|
+
aria-label={`View invoice: ${invoice.title}`}
|
|
135
|
+
>
|
|
136
|
+
View
|
|
137
|
+
</button>
|
|
138
|
+
</td>
|
|
139
|
+
</tr>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema for the InvoiceTable example.
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the standards-preferred pattern:
|
|
5
|
+
* - Single Zod schema as source of truth
|
|
6
|
+
* - Inferred TypeScript type from the schema
|
|
7
|
+
* - Shared between FE and BE (in a real project this would live in shared/schemas/)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
export const invoiceSchema = z.object({
|
|
13
|
+
id: z.string().cuid(),
|
|
14
|
+
title: z.string().min(1).max(200),
|
|
15
|
+
status: z.enum(["draft", "open", "paid", "void"]),
|
|
16
|
+
amount: z.number().nonnegative(),
|
|
17
|
+
dueDate: z.coerce.date(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type Invoice = z.infer<typeof invoiceSchema>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GOOD: Context provides user data where it's actually needed
|
|
3
|
+
*
|
|
4
|
+
* - Intermediate components carry no extra props
|
|
5
|
+
* - Adding new shared data doesn't require updating every layer
|
|
6
|
+
* - Tree is composed declaratively at the top
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createContext, useContext, type ReactNode } from "react";
|
|
10
|
+
|
|
11
|
+
interface User {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UserContextValue {
|
|
17
|
+
user: User;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const UserContext = createContext<UserContextValue | null>(null);
|
|
21
|
+
|
|
22
|
+
interface UserProviderProps {
|
|
23
|
+
user: User;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function UserProvider({ user, children }: UserProviderProps) {
|
|
28
|
+
return <UserContext.Provider value={{ user }}>{children}</UserContext.Provider>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function useCurrentUser(): User {
|
|
32
|
+
const context = useContext(UserContext);
|
|
33
|
+
if (!context) throw new Error("useCurrentUser must be used within UserProvider");
|
|
34
|
+
return context.user;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Components — no `user` prop forwarded through the tree ───────────────────
|
|
38
|
+
|
|
39
|
+
interface PageProps {
|
|
40
|
+
user: User;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function Page({ user }: PageProps) {
|
|
44
|
+
return (
|
|
45
|
+
<UserProvider user={user}>
|
|
46
|
+
<Layout />
|
|
47
|
+
</UserProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Layout() {
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<Header />
|
|
55
|
+
<Sidebar />
|
|
56
|
+
<Content />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Sidebar() {
|
|
62
|
+
return (
|
|
63
|
+
<aside>
|
|
64
|
+
<Nav />
|
|
65
|
+
<UserMenu />
|
|
66
|
+
</aside>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function UserMenu() {
|
|
71
|
+
const user = useCurrentUser();
|
|
72
|
+
return <button type="button">{user.name}</button>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Header() {
|
|
76
|
+
return <header />;
|
|
77
|
+
}
|
|
78
|
+
function Content() {
|
|
79
|
+
return <main />;
|
|
80
|
+
}
|
|
81
|
+
function Nav() {
|
|
82
|
+
return <nav />;
|
|
83
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# AI Agent Behavior
|
|
2
|
+
|
|
3
|
+
Rules for AI agents (Claude Code, Cursor, Continue, Copilot, etc.) working within a codebase governed by these standards.
|
|
4
|
+
|
|
5
|
+
## Stack Selection
|
|
6
|
+
|
|
7
|
+
The primary stack is **TypeScript on Node.js with React on the frontend**. Default to this stack for:
|
|
8
|
+
|
|
9
|
+
- New projects
|
|
10
|
+
- New services or modules
|
|
11
|
+
- Greenfield features
|
|
12
|
+
- Any task where the language is not already chosen
|
|
13
|
+
|
|
14
|
+
**PHP standards exist in [`../php/`](../php/) but are secondary.** Load and apply them only when:
|
|
15
|
+
|
|
16
|
+
- The user explicitly asks for PHP
|
|
17
|
+
- You are working inside an existing PHP codebase
|
|
18
|
+
- The task involves modifying a PHP file
|
|
19
|
+
|
|
20
|
+
Do not propose rewriting a Node/TypeScript codebase in PHP. Do not suggest PHP as an alternative to JavaScript/TypeScript "for performance" or "for simplicity." If the user has not specified PHP, they want TypeScript.
|
|
21
|
+
|
|
22
|
+
## PRIORITIZE
|
|
23
|
+
|
|
24
|
+
- Readability
|
|
25
|
+
- Accessibility
|
|
26
|
+
- Composability
|
|
27
|
+
- Maintainability
|
|
28
|
+
- Type safety
|
|
29
|
+
- Feature isolation
|
|
30
|
+
- Incremental refactoring
|
|
31
|
+
- Explicit APIs
|
|
32
|
+
- Predictable architecture
|
|
33
|
+
|
|
34
|
+
## AVOID
|
|
35
|
+
|
|
36
|
+
- Giant files (trigger decomposition review)
|
|
37
|
+
- `useEffect` abuse (derived state, state sync, data orchestration)
|
|
38
|
+
- Premature abstractions
|
|
39
|
+
- Hook mini-frameworks
|
|
40
|
+
- Defensive memoization
|
|
41
|
+
- Over-generalized utilities
|
|
42
|
+
- Excessive inheritance
|
|
43
|
+
- Redux
|
|
44
|
+
- Hidden side effects
|
|
45
|
+
- Implementation testing (test behavior, not internals)
|
|
46
|
+
- State synchronization effects
|
|
47
|
+
- Aggressive rewrites without explicit approval
|
|
48
|
+
|
|
49
|
+
## REFACTORING RULES
|
|
50
|
+
|
|
51
|
+
### DO
|
|
52
|
+
|
|
53
|
+
- Preserve behavior when refactoring
|
|
54
|
+
- Prefer additive refactors (add new, migrate, delete old)
|
|
55
|
+
- Write tests before refactoring untested code where possible
|
|
56
|
+
- Preserve architecture boundaries
|
|
57
|
+
- Migrate legacy systems gently and incrementally
|
|
58
|
+
|
|
59
|
+
### DO NOT
|
|
60
|
+
|
|
61
|
+
- Perform massive rewrites unless explicitly requested
|
|
62
|
+
- Introduce major architectural divergence without asking first
|
|
63
|
+
- Break existing behavior in the name of improvement
|
|
64
|
+
- Rename aggressively across unrelated files in a single PR
|
|
65
|
+
|
|
66
|
+
## ASKING VS. PROCEEDING
|
|
67
|
+
|
|
68
|
+
Ask before:
|
|
69
|
+
- Changing state management strategy
|
|
70
|
+
- Reorganizing folder structure significantly
|
|
71
|
+
- Replacing a library or major dependency
|
|
72
|
+
- Changing the testing approach for a module
|
|
73
|
+
- Any action that affects more than the immediate task
|
|
74
|
+
|
|
75
|
+
Proceed without asking for:
|
|
76
|
+
- Small, contained refactors within a single file
|
|
77
|
+
- Fixing obvious bugs that are clearly unintentional
|
|
78
|
+
- Adding types to untyped code
|
|
79
|
+
- Improving accessibility of existing components
|
|
80
|
+
|
|
81
|
+
## OUTPUT QUALITY EXPECTATIONS
|
|
82
|
+
|
|
83
|
+
- Every change must leave the codebase at least as readable as it was found
|
|
84
|
+
- Every change must leave the codebase at least as accessible as it was found
|
|
85
|
+
- No regressions in type coverage
|
|
86
|
+
- No new `any` types without explicit justification
|
|
87
|
+
- No new `useEffect` without a clear external synchronization reason
|
|
88
|
+
|
|
89
|
+
## See Also
|
|
90
|
+
|
|
91
|
+
- [`core-principles.md`](core-principles.md) — the architectural values these rules implement
|
|
92
|
+
- [`../architecture/refactoring.md`](../architecture/refactoring.md) — incremental refactoring rules
|
|
93
|
+
- [`../react/anti-patterns.md`](../react/anti-patterns.md) — patterns to avoid in React code
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Core Engineering Principles
|
|
2
|
+
|
|
3
|
+
## Core Principle
|
|
4
|
+
|
|
5
|
+
Prefer explicit, composable, feature-oriented architecture over generalized abstractions and centralized complexity.
|
|
6
|
+
|
|
7
|
+
## Architectural Values
|
|
8
|
+
|
|
9
|
+
### DO
|
|
10
|
+
|
|
11
|
+
- Composition over inheritance
|
|
12
|
+
- Readability over cleverness
|
|
13
|
+
- Explicitness over magic
|
|
14
|
+
- Feature ownership over centralized organization
|
|
15
|
+
- Incremental abstraction over premature DRY
|
|
16
|
+
- Maintainability over premature optimization
|
|
17
|
+
- Colocation over fragmentation
|
|
18
|
+
- Accessibility-first development
|
|
19
|
+
- Strong type safety
|
|
20
|
+
- Predictable data flow
|
|
21
|
+
|
|
22
|
+
### PRIORITY
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Readability > Maintainability > Performance > Cleverness
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Applying These Values
|
|
29
|
+
|
|
30
|
+
**Colocation** — Keep feature-specific code inside the feature. Move to shared only when genuinely reused by 3+ features.
|
|
31
|
+
|
|
32
|
+
**Incrementalism** — Start simple. Add abstraction when the need is proven, not anticipated.
|
|
33
|
+
|
|
34
|
+
**Explicitness** — Prefer code that is obvious over code that is elegant. A future reader should not need to trace magic to understand behavior.
|
|
35
|
+
|
|
36
|
+
**Feature ownership** — A feature folder owns its components, hooks, types, and tests. It does not scatter logic across the codebase by type.
|
|
37
|
+
|
|
38
|
+
**Accessibility first** — Semantic HTML and keyboard support are not optional. They are part of the definition of done.
|
|
39
|
+
|
|
40
|
+
**Type safety** — Types are documentation and safety nets. Avoid `any`, type assertions, and type gymnastics.
|
|
41
|
+
|
|
42
|
+
**Predictable data flow** — Data flows downward. Side effects are explicit and isolated. State changes are traceable.
|
|
43
|
+
|
|
44
|
+
## See Also
|
|
45
|
+
|
|
46
|
+
- [`ai-agent-behavior.md`](ai-agent-behavior.md) — concrete rules derived from these principles
|
|
47
|
+
- [`maintainability.md`](maintainability.md) — colocation, deletability, naming
|
|
48
|
+
- [`readability.md`](readability.md) — obvious over clever
|
|
49
|
+
- [`incremental-abstraction.md`](incremental-abstraction.md) — when to abstract vs. inline
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Incremental Abstraction
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Abstract when the need is proven. Inline when the need is uncertain.
|
|
6
|
+
|
|
7
|
+
## The Three-Instance Rule
|
|
8
|
+
|
|
9
|
+
Do not extract a shared abstraction until you have at least three real, distinct use cases. Two similar things may be coincidence. Three is a pattern.
|
|
10
|
+
|
|
11
|
+
## Progression
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Inline → Local function → Shared utility → Published package
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Move right only when left is insufficient. Do not start at the right.
|
|
18
|
+
|
|
19
|
+
## DO
|
|
20
|
+
|
|
21
|
+
- Duplicate small pieces of logic until the pattern is clear
|
|
22
|
+
- Extract to a local function first, before considering a shared module
|
|
23
|
+
- Name extracted abstractions after what they do, not what they contain
|
|
24
|
+
- Keep abstractions small and single-purpose
|
|
25
|
+
|
|
26
|
+
## DO NOT
|
|
27
|
+
|
|
28
|
+
- Create a utility file on the first use
|
|
29
|
+
- Extract "just in case" it is needed elsewhere
|
|
30
|
+
- Build generic frameworks from a single use case
|
|
31
|
+
- Name abstractions by what they wrap (`useUserData` wrapping `useQuery` is not an abstraction — it is indirection)
|
|
32
|
+
|
|
33
|
+
## Signs You Are Abstracting Too Early
|
|
34
|
+
|
|
35
|
+
- The abstraction has only one caller
|
|
36
|
+
- The abstraction has more configuration options than callers
|
|
37
|
+
- You are passing flags to switch between behaviors (`isEdit`, `mode: 'create' | 'edit'`)
|
|
38
|
+
- The abstraction is harder to understand than the code it replaces
|
|
39
|
+
|
|
40
|
+
## Signs You Should Abstract Now
|
|
41
|
+
|
|
42
|
+
- Three or more places duplicate the same logic with minor variations
|
|
43
|
+
- A bug was fixed in one place but exists in two others
|
|
44
|
+
- A feature requirement forced you to update the same logic in multiple files
|
|
45
|
+
|
|
46
|
+
## PRIORITY
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Clarity of local code > DRY
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## See Also
|
|
53
|
+
|
|
54
|
+
- [`maintainability.md`](maintainability.md) — deletability and naming
|
|
55
|
+
- [`../architecture/shared-code.md`](../architecture/shared-code.md) — when to promote to `shared/`
|
|
56
|
+
- [`../architecture/refactoring.md`](../architecture/refactoring.md) — additive refactor strategy
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Maintainability
|
|
2
|
+
|
|
3
|
+
## Core Rule
|
|
4
|
+
|
|
5
|
+
Write code that the next engineer (or AI agent) can understand, change, and delete safely.
|
|
6
|
+
|
|
7
|
+
## DO
|
|
8
|
+
|
|
9
|
+
- Colocate feature-specific logic with the feature
|
|
10
|
+
- Delete dead code rather than commenting it out
|
|
11
|
+
- Name things after their intent, not their implementation
|
|
12
|
+
- Keep functions and components focused on a single responsibility
|
|
13
|
+
- Make dependencies explicit (imports, props, parameters)
|
|
14
|
+
- Prefer flat structures over deep nesting
|
|
15
|
+
- Write the simplest thing that works
|
|
16
|
+
|
|
17
|
+
## DO NOT
|
|
18
|
+
|
|
19
|
+
- Leave commented-out code without a dated explanation
|
|
20
|
+
- Create files that "might be useful later"
|
|
21
|
+
- Hide behavior inside abstractions named too generically (`utils.ts`, `helpers.ts`, `common.ts`)
|
|
22
|
+
- Use abbreviations that require domain knowledge to decode
|
|
23
|
+
- Rely on implicit global state or module-level side effects
|
|
24
|
+
- Write clever code when obvious code works
|
|
25
|
+
|
|
26
|
+
## File Size
|
|
27
|
+
|
|
28
|
+
Large files are a signal, not a rule violation. Review for decomposition when a file:
|
|
29
|
+
- Exceeds ~300 lines
|
|
30
|
+
- Has multiple unrelated responsibilities
|
|
31
|
+
- Is difficult to scan or navigate
|
|
32
|
+
|
|
33
|
+
Do not split files purely to hit a line count target.
|
|
34
|
+
|
|
35
|
+
## Naming
|
|
36
|
+
|
|
37
|
+
- Name functions after what they do: `formatCurrency`, `fetchUserProfile`
|
|
38
|
+
- Name components after what they render: `InvoiceTable`, `UserAvatarGroup`
|
|
39
|
+
- Name hooks after what behavior they encapsulate: `useScrollLock`, `useDebounce`
|
|
40
|
+
- Avoid: `handleStuff`, `doThing`, `myHelper`, `useData`
|
|
41
|
+
|
|
42
|
+
## Colocation
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
features/
|
|
46
|
+
invoices/
|
|
47
|
+
InvoiceTable.tsx ← component
|
|
48
|
+
InvoiceTable.test.tsx ← test alongside component
|
|
49
|
+
useInvoiceSort.ts ← hook used only here
|
|
50
|
+
invoice.types.ts ← types scoped to this feature
|
|
51
|
+
invoice.api.ts ← data fetching scoped to this feature
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Move to `shared/` only when used across 3+ features.
|
|
55
|
+
|
|
56
|
+
## PRIORITY
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Deletability > Reusability > Cleverness
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## See Also
|
|
63
|
+
|
|
64
|
+
- [`readability.md`](readability.md) — short files, clear names, flat structure
|
|
65
|
+
- [`incremental-abstraction.md`](incremental-abstraction.md) — abstract late, not early
|
|
66
|
+
- [`../architecture/feature-driven.md`](../architecture/feature-driven.md) — colocation as a structural pattern
|