@webjskit/cli 0.1.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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * SaaS template files for `webjs create --template saas`.
3
+ * Extracted to avoid nested template literal escaping issues.
4
+ */
5
+
6
+ import { mkdir, writeFile } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+
9
+ /**
10
+ * @param {string} appDir
11
+ */
12
+ export async function writeSaasFiles(appDir) {
13
+ // lib/prisma.ts
14
+ await mkdir(join(appDir, 'lib'), { recursive: true });
15
+ await writeFile(join(appDir, 'lib', 'prisma.ts'), [
16
+ "import { PrismaClient } from '@prisma/client';",
17
+ "",
18
+ "const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };",
19
+ "export const prisma = globalForPrisma.prisma || new PrismaClient();",
20
+ "if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;",
21
+ "",
22
+ ].join('\n'));
23
+
24
+ // lib/password.ts
25
+ await writeFile(join(appDir, 'lib', 'password.ts'), [
26
+ "import { scrypt, randomBytes, timingSafeEqual } from 'node:crypto';",
27
+ "import { promisify } from 'node:util';",
28
+ "",
29
+ "const scryptAsync = promisify(scrypt);",
30
+ "",
31
+ "export async function hash(password: string): Promise<string> {",
32
+ " const salt = randomBytes(16).toString('hex');",
33
+ " const buf = (await scryptAsync(password, salt, 64)) as Buffer;",
34
+ " return salt + ':' + buf.toString('hex');",
35
+ "}",
36
+ "",
37
+ "export async function compare(password: string, stored: string): Promise<boolean> {",
38
+ " const [salt, key] = stored.split(':');",
39
+ " const buf = (await scryptAsync(password, salt, 64)) as Buffer;",
40
+ " return timingSafeEqual(buf, Buffer.from(key, 'hex'));",
41
+ "}",
42
+ "",
43
+ ].join('\n'));
44
+
45
+ // lib/auth.ts
46
+ await writeFile(join(appDir, 'lib', 'auth.ts'), [
47
+ "import { createAuth, Credentials } from '@webjskit/server';",
48
+ "import { prisma } from './prisma.ts';",
49
+ "import { compare } from './password.ts';",
50
+ "",
51
+ "export const { auth, signIn, signOut, handlers } = createAuth({",
52
+ " providers: [",
53
+ " Credentials({",
54
+ " async authorize(credentials: { email: string; password: string }) {",
55
+ " const user = await prisma.user.findUnique({ where: { email: credentials.email } });",
56
+ " if (!user || !await compare(credentials.password, user.passwordHash)) return null;",
57
+ " return { id: String(user.id), name: user.name, email: user.email };",
58
+ " },",
59
+ " }),",
60
+ " ],",
61
+ " secret: process.env.AUTH_SECRET,",
62
+ "});",
63
+ "",
64
+ ].join('\n'));
65
+
66
+ // prisma/schema.prisma
67
+ await mkdir(join(appDir, 'prisma'), { recursive: true });
68
+ await writeFile(join(appDir, 'prisma', 'schema.prisma'), [
69
+ 'datasource db {',
70
+ ' provider = "sqlite"',
71
+ ' url = env("DATABASE_URL")',
72
+ '}',
73
+ '',
74
+ 'generator client {',
75
+ ' provider = "prisma-client-js"',
76
+ '}',
77
+ '',
78
+ 'model User {',
79
+ ' id Int @id @default(autoincrement())',
80
+ ' email String @unique',
81
+ ' name String?',
82
+ ' passwordHash String',
83
+ ' createdAt DateTime @default(now())',
84
+ '}',
85
+ '',
86
+ ].join('\n'));
87
+
88
+ // modules/auth/actions/signup.server.ts
89
+ await mkdir(join(appDir, 'modules', 'auth', 'actions'), { recursive: true });
90
+ await mkdir(join(appDir, 'modules', 'auth', 'queries'), { recursive: true });
91
+
92
+ await writeFile(join(appDir, 'modules', 'auth', 'actions', 'signup.server.ts'), [
93
+ "'use server';",
94
+ "",
95
+ "import { prisma } from '../../../lib/prisma.ts';",
96
+ "import { hash } from '../../../lib/password.ts';",
97
+ "",
98
+ "export async function signup(input: { name: string; email: string; password: string }) {",
99
+ " const exists = await prisma.user.findUnique({ where: { email: input.email } });",
100
+ " if (exists) return { success: false as const, error: 'Email already registered', status: 409 };",
101
+ " const user = await prisma.user.create({",
102
+ " data: { name: input.name, email: input.email, passwordHash: await hash(input.password) },",
103
+ " });",
104
+ " return { success: true as const, data: { id: user.id, name: user.name, email: user.email } };",
105
+ "}",
106
+ "",
107
+ ].join('\n'));
108
+
109
+ // modules/auth/queries/current-user.server.ts
110
+ await writeFile(join(appDir, 'modules', 'auth', 'queries', 'current-user.server.ts'), [
111
+ "'use server';",
112
+ "",
113
+ "import { auth } from '../../../lib/auth.ts';",
114
+ "",
115
+ "export async function currentUser() {",
116
+ " const session = await auth();",
117
+ " return session?.user ?? null;",
118
+ "}",
119
+ "",
120
+ ].join('\n'));
121
+
122
+ // modules/auth/types.ts
123
+ await writeFile(join(appDir, 'modules', 'auth', 'types.ts'), [
124
+ "export interface User {",
125
+ " id: number;",
126
+ " name: string | null;",
127
+ " email: string;",
128
+ "}",
129
+ "",
130
+ "export type ActionResult<T> =",
131
+ " | { success: true; data: T }",
132
+ " | { success: false; error: string; status: number };",
133
+ "",
134
+ ].join('\n'));
135
+
136
+ // app/api/auth/[...path]/route.ts
137
+ await mkdir(join(appDir, 'app', 'api', 'auth', '[...path]'), { recursive: true });
138
+ await writeFile(join(appDir, 'app', 'api', 'auth', '[...path]', 'route.ts'), [
139
+ "import { handlers } from '../../../../../lib/auth.ts';",
140
+ "export const GET = handlers.GET;",
141
+ "export const POST = handlers.POST;",
142
+ "",
143
+ ].join('\n'));
144
+
145
+ // app/login/page.ts
146
+ await mkdir(join(appDir, 'app', 'login'), { recursive: true });
147
+ await writeFile(join(appDir, 'app', 'login', 'page.ts'), [
148
+ "import { html } from '@webjskit/core';",
149
+ "",
150
+ "export const metadata = { title: 'Login' };",
151
+ "",
152
+ "export default function LoginPage() {",
153
+ " return html`",
154
+ " <h1>Login</h1>",
155
+ " <form method=\"POST\" action=\"/api/auth/callback/credentials\">",
156
+ " <label>Email <input type=\"email\" name=\"email\" required></label>",
157
+ " <label>Password <input type=\"password\" name=\"password\" required></label>",
158
+ " <button type=\"submit\">Sign in</button>",
159
+ " </form>",
160
+ " <p>Don't have an account? <a href=\"/signup\">Sign up</a></p>",
161
+ " `;",
162
+ "}",
163
+ "",
164
+ ].join('\n'));
165
+
166
+ // app/signup/page.ts
167
+ await mkdir(join(appDir, 'app', 'signup'), { recursive: true });
168
+ await writeFile(join(appDir, 'app', 'signup', 'page.ts'), [
169
+ "import { html } from '@webjskit/core';",
170
+ "",
171
+ "export const metadata = { title: 'Sign up' };",
172
+ "",
173
+ "export default function SignupPage() {",
174
+ " return html`",
175
+ " <h1>Sign up</h1>",
176
+ " <form id=\"signup-form\">",
177
+ " <label>Name <input type=\"text\" name=\"name\" required></label>",
178
+ " <label>Email <input type=\"email\" name=\"email\" required></label>",
179
+ " <label>Password <input type=\"password\" name=\"password\" required minlength=\"8\"></label>",
180
+ " <button type=\"submit\">Create account</button>",
181
+ " </form>",
182
+ " <p>Already have an account? <a href=\"/login\">Log in</a></p>",
183
+ " `;",
184
+ "}",
185
+ "",
186
+ ].join('\n'));
187
+
188
+ // app/dashboard/middleware.ts
189
+ await mkdir(join(appDir, 'app', 'dashboard', 'settings'), { recursive: true });
190
+ await writeFile(join(appDir, 'app', 'dashboard', 'middleware.ts'), [
191
+ "import { auth } from '../../lib/auth.ts';",
192
+ "",
193
+ "export default async function requireAuth(req: Request, next: () => Promise<Response>) {",
194
+ " const session = await auth();",
195
+ " if (!session?.user) {",
196
+ " return new Response(null, { status: 302, headers: { location: '/login' } });",
197
+ " }",
198
+ " return next();",
199
+ "}",
200
+ "",
201
+ ].join('\n'));
202
+
203
+ // app/dashboard/page.ts
204
+ await writeFile(join(appDir, 'app', 'dashboard', 'page.ts'), [
205
+ "import { html } from '@webjskit/core';",
206
+ "import { currentUser } from '../../modules/auth/queries/current-user.server.ts';",
207
+ "",
208
+ "export const metadata = { title: 'Dashboard' };",
209
+ "",
210
+ "export default async function Dashboard() {",
211
+ " const user = await currentUser();",
212
+ " return html`",
213
+ " <h1>Dashboard</h1>",
214
+ " <p>Welcome, ${`\\$\\{user?.name || user?.email\\}`}!</p>",
215
+ " <a href=\"/dashboard/settings\">Settings</a>",
216
+ " `;",
217
+ "}",
218
+ "",
219
+ ].join('\n'));
220
+
221
+ // app/dashboard/settings/page.ts
222
+ await writeFile(join(appDir, 'app', 'dashboard', 'settings', 'page.ts'), [
223
+ "import { html } from '@webjskit/core';",
224
+ "import { currentUser } from '../../../modules/auth/queries/current-user.server.ts';",
225
+ "",
226
+ "export const metadata = { title: 'Settings' };",
227
+ "",
228
+ "export default async function Settings() {",
229
+ " const user = await currentUser();",
230
+ " return html`",
231
+ " <h1>Settings</h1>",
232
+ " <p>Email: ${`\\$\\{user?.email\\}`}</p>",
233
+ " <p>Name: ${`\\$\\{user?.name || 'Not set'\\}`}</p>",
234
+ " `;",
235
+ "}",
236
+ "",
237
+ ].join('\n'));
238
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@webjskit/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "webjs CLI — dev, start, create, db",
6
+ "bin": {
7
+ "webjs": "bin/webjs.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ "templates",
13
+ "README.md"
14
+ ],
15
+ "dependencies": {
16
+ "@webjskit/server": "0.1.0"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/vivek7405/webjs.git",
24
+ "directory": "packages/cli"
25
+ },
26
+ "homepage": "https://github.com/vivek7405/webjs#readme",
27
+ "bugs": "https://github.com/vivek7405/webjs/issues",
28
+ "license": "MIT",
29
+ "keywords": [
30
+ "webjs",
31
+ "cli",
32
+ "dev",
33
+ "scaffold"
34
+ ]
35
+ }
@@ -0,0 +1,39 @@
1
+ #!/bin/bash
2
+ #
3
+ # guard-branch-context.sh — Claude Code PreToolUse hook
4
+ #
5
+ # Rules:
6
+ # - On main/master → ask (agent should create a feature branch first)
7
+ # - On any other branch → allow (feature branches are free to edit)
8
+ # - Bypass mode → allow everything
9
+
10
+ INPUT=$(cat /dev/stdin)
11
+
12
+ # Bypass mode — full autonomy
13
+ SETTINGS="$HOME/.claude/settings.json"
14
+ if [ -f "$SETTINGS" ]; then
15
+ BYPASS=$(jq -r '.skipDangerousModePermissionPrompt // false' "$SETTINGS" 2>/dev/null)
16
+ if [ "$BYPASS" = "true" ]; then
17
+ exit 0
18
+ fi
19
+ fi
20
+
21
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
22
+ exit 0
23
+ fi
24
+
25
+ BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
26
+ [ -z "$BRANCH" ] && exit 0
27
+
28
+ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
29
+ jq -n --arg reason "You are on '$BRANCH'. Create a feature branch first (git checkout -b feature/<name>), or approve to edit on '$BRANCH'." '{
30
+ hookSpecificOutput: {
31
+ hookEventName: "PreToolUse",
32
+ permissionDecision: "ask",
33
+ permissionDecisionReason: $reason
34
+ }
35
+ }'
36
+ exit 0
37
+ fi
38
+
39
+ exit 0
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ #
3
+ # guard-main-merge.sh — Claude Code PreToolUse hook
4
+ #
5
+ # Rules:
6
+ # - git merge → ask (merging to parent branch needs approval)
7
+ # - git push on a feature branch → allow (free to push)
8
+ # - git push targeting main → ask
9
+ # - Bypass mode → allow everything
10
+
11
+ COMMAND=$(jq -r '.tool_input.command // empty' < /dev/stdin)
12
+ [ -z "$COMMAND" ] && exit 0
13
+
14
+ # Bypass mode — full autonomy
15
+ SETTINGS="$HOME/.claude/settings.json"
16
+ if [ -f "$SETTINGS" ]; then
17
+ BYPASS=$(jq -r '.skipDangerousModePermissionPrompt // false' "$SETTINGS" 2>/dev/null)
18
+ if [ "$BYPASS" = "true" ]; then
19
+ exit 0
20
+ fi
21
+ fi
22
+
23
+ NORMALIZED=$(printf '%s' "$COMMAND" | tr -s '[:space:]' ' ')
24
+
25
+ ask_with_reason() {
26
+ jq -n --arg reason "$1" '{
27
+ hookSpecificOutput: {
28
+ hookEventName: "PreToolUse",
29
+ permissionDecision: "ask",
30
+ permissionDecisionReason: $reason
31
+ }
32
+ }'
33
+ exit 0
34
+ }
35
+
36
+ if [[ "$NORMALIZED" == *"git merge"* ]]; then
37
+ ask_with_reason "This command contains 'git merge'. Merging requires approval. After merging, should the source branch be deleted or kept? Approve to proceed."
38
+ fi
39
+
40
+ if [[ "$NORMALIZED" == *"git push"* ]] && [[ "$NORMALIZED" == *"main"* ]]; then
41
+ ask_with_reason "This looks like 'git push' targeting main. Approve to proceed."
42
+ fi
43
+
44
+ exit 0
@@ -0,0 +1,24 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Bash",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": ".claude/hooks/guard-main-merge.sh"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "matcher": "Edit|Write",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": ".claude/hooks/guard-branch-context.sh"
19
+ }
20
+ ]
21
+ }
22
+ ]
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "playwright": {
4
+ "type": "stdio",
5
+ "command": "npx",
6
+ "args": ["@playwright/mcp@latest"]
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,63 @@
1
+ # Cursor Rules — webjs app
2
+
3
+ You are working on a webjs app — an AI-first, no-build, web-components-first
4
+ framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
5
+ project-specific conventions before writing any code.
6
+
7
+ ## Before starting ANY work
8
+
9
+ FIRST, before writing any code:
10
+ 1. Run `git branch --show-current` to check the branch.
11
+ - If on main/master: STOP. Ask the user which branch to use, or create
12
+ one with `git checkout -b feature/<name>`.
13
+ - If on a feature branch: verify it matches the current task. Ask if unsure.
14
+ 2. Sync with parent: `git fetch origin && git log HEAD..origin/main --oneline`
15
+ - If upstream has new commits: `git rebase origin/main` before starting.
16
+ - Resolve any conflicts before proceeding with the task.
17
+
18
+ ## Autonomous mode (sandbox / no-prompt mode)
19
+
20
+ If running without interactive approval, auto-decide:
21
+ - On main? Auto-create feature/<task-slug> branch
22
+ - Parent has new commits? Auto-rebase before starting
23
+ - Merge? Auto-merge in autonomous mode, delete feature branches after
24
+ - Commit message? Auto-generate (meaningful, no AI attribution)
25
+ - Tests failing? Fix them. Convention violations? Fix them.
26
+ Quality bar stays the same — just no blocking on questions.
27
+
28
+ ## Mandatory workflow (never skip)
29
+
30
+ 1. TESTS: Server tests in test/unit/ (node:test), browser tests in
31
+ test/browser/ (WTR + Playwright, real Chromium). Run `npx webjs test`
32
+ after every change. Never deliver code without passing tests.
33
+
34
+ 2. DOCS: Update AGENTS.md for API changes. Update docs/ and website/ if
35
+ they exist. The user should never have to ask for tests or docs.
36
+
37
+ 3. CONVENTIONS: Run `npx webjs check` and fix violations before committing.
38
+
39
+ ## Git rules
40
+
41
+ - COMMIT AND PUSH OFTEN. After each logical unit of work — don't accumulate
42
+ changes. Always `git push` after committing. This is automatic.
43
+ - Write meaningful commit messages: what changed and why, not "update files"
44
+ - NEVER add "Co-Authored-By", "Generated by", "AI-assisted" or similar
45
+ attribution trailers to commits
46
+ - Work on feature branches, not main
47
+ - NEVER push directly to main — create a pull request
48
+ - NEVER merge any branch without explicit user permission. Always ask:
49
+ "Ready to merge <branch> into <target>? Delete or keep <branch> after?"
50
+ Wait for approval AND the delete/keep preference before proceeding.
51
+ This applies to ALL merges, not just merges into main.
52
+ - Run tests before every commit
53
+ - Keep commits small and focused
54
+
55
+ ## Framework rules
56
+
57
+ - No build step: source files are served as ES modules
58
+ - Web components with shadow DOM: use `static styles = css` not inline styles
59
+ - One function per server action file (*.server.ts)
60
+ - Components must call customElements.define('tag', Class)
61
+ - Never import @prisma/client or node:* from client components
62
+ - Use directives (classMap, styleMap, ref, etc.) from '@webjskit/core/directives'
63
+ - See AGENTS.md for the complete directive decision guide
@@ -0,0 +1,18 @@
1
+ # EditorConfig — consistent formatting across editors and AI agents
2
+ # https://editorconfig.org
3
+
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = true
12
+ insert_final_newline = true
13
+
14
+ [*.md]
15
+ trim_trailing_whitespace = false
16
+
17
+ [Makefile]
18
+ indent_style = tab
@@ -0,0 +1,27 @@
1
+ # webjs environment variables
2
+ # Copy to .env and fill in your values: cp .env.example .env
3
+ #
4
+ # Opinionated defaults: set these and the framework auto-configures.
5
+
6
+ # ── Server ──────────────────────────────────────────────────────────
7
+ PORT=3000
8
+
9
+ # ── Auth (required for authentication) ──────────────────────────────
10
+ # Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
11
+ AUTH_SECRET=
12
+
13
+ # ── OAuth providers (optional — enable the ones you need) ───────────
14
+ # AUTH_GOOGLE_ID=
15
+ # AUTH_GOOGLE_SECRET=
16
+ # AUTH_GITHUB_ID=
17
+ # AUTH_GITHUB_SECRET=
18
+
19
+ # ── Redis (production scaling) ──────────────────────────────────────
20
+ # Set this and cache, sessions, rate limiting, and pub/sub all
21
+ # automatically use Redis. Without it, everything uses in-memory
22
+ # defaults (great for development).
23
+ # REDIS_URL=redis://localhost:6379
24
+
25
+ # ── Database ────────────────────────────────────────────────────────
26
+ # Used by Prisma. SQLite for dev, PostgreSQL/MySQL for production.
27
+ DATABASE_URL=file:./dev.db
@@ -0,0 +1,59 @@
1
+ # GitHub Copilot Instructions — webjs app
2
+
3
+ You are working on a webjs app — an AI-first, no-build, web-components-first
4
+ framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
5
+ project-specific conventions.
6
+
7
+ ## Before starting ANY work
8
+
9
+ FIRST, before writing any code:
10
+ 1. Check `git branch --show-current`.
11
+ - If on main/master: create a feature branch before editing.
12
+ - If on a feature branch: verify it matches the task at hand.
13
+ 2. Sync: `git fetch origin && git rebase origin/main` if behind.
14
+
15
+ ## Autonomous mode
16
+
17
+ If running without interactive approval (sandbox, auto-approve, etc.):
18
+ - On main? Auto-create feature/<task-slug> branch
19
+ - Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
20
+ - Auto-generate meaningful commit messages. Fix tests and violations.
21
+
22
+ ## Mandatory workflow
23
+
24
+ Every code change must include:
25
+ 1. Commit and push — COMMIT AND PUSH after each logical unit of work.
26
+ Always `git push` after committing. Don't accumulate changes. Automatic.
27
+ 2. Server tests in test/unit/*.test.ts (node:test for actions, queries, utilities)
28
+ 3. Browser tests in test/browser/*.test.js (WTR + Playwright, real Chromium)
29
+ 4. Documentation updates (AGENTS.md for API, docs/ for user guides)
30
+ 5. Convention validation: `npx webjs check` must pass
31
+
32
+ ## Git rules
33
+
34
+ - Commit after each logical unit of work
35
+ - Meaningful commit messages: what changed and why
36
+ - NEVER add Co-Authored-By or AI attribution trailers to commits
37
+ - Work on feature branches, create PRs, never push directly to main
38
+ - NEVER merge any branch without explicit user permission. Always ask:
39
+ "Ready to merge <branch> into <target>? Delete or keep <branch> after?"
40
+ Wait for approval AND the delete/keep preference. Applies to ALL merges.
41
+ - Run `npx webjs test` before every commit
42
+
43
+ ## Code patterns
44
+
45
+ - Tagged template: html`<div>${value}</div>` with css`...` for styles
46
+ - Components: extend WebComponent, use static tag/styles/properties, call Class.register('tag')
47
+ - Server actions: *.server.ts files with one exported async function each
48
+ - Directives: import { classMap, styleMap, ref, when, ... } from '@webjskit/core/directives'
49
+ - Context: import { createContext, ContextProvider, ContextConsumer } from '@webjskit/core/context'
50
+ - Task: import { Task, TaskStatus } from '@webjskit/core/task'
51
+ - Routing: file-based under app/ (page.ts, layout.ts, route.ts, middleware.ts)
52
+
53
+ ## What NOT to do
54
+
55
+ - Don't introduce build tools or bundlers in the critical path
56
+ - Don't import @prisma/client or node:* from client components
57
+ - Don't use inline style="..." on components (use static styles = css`...`)
58
+ - Don't mutate this.state directly (use this.setState())
59
+ - Don't skip tests or documentation updates
@@ -0,0 +1,14 @@
1
+ ## Summary
2
+
3
+ <!-- What does this PR do? 1-3 bullet points. -->
4
+
5
+ ## Test plan
6
+
7
+ - [ ] Unit tests added/updated (`npx webjs test` passes)
8
+ - [ ] E2E tests added/updated for user-facing changes (`npx webjs test --e2e` passes)
9
+ - [ ] `npx webjs check` passes (no convention violations)
10
+
11
+ ## Documentation
12
+
13
+ - [ ] AGENTS.md updated (if API surface changed)
14
+ - [ ] Docs updated (if docs/ exists and feature is documented)
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ #
3
+ # pre-commit hook — blocks commits on main/master.
4
+ #
5
+ # No AI agent, no editor, no human can commit to main directly.
6
+ # Create a feature branch first. This is git-level enforcement.
7
+ #
8
+ # To bypass in emergencies: git commit --no-verify
9
+
10
+ BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
11
+
12
+ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
13
+ echo ""
14
+ echo "ERROR: Cannot commit directly to '$BRANCH'."
15
+ echo ""
16
+ echo "Create a feature branch first:"
17
+ echo " git checkout -b feature/<name>"
18
+ echo ""
19
+ echo "To bypass (emergencies only): git commit --no-verify"
20
+ echo ""
21
+ exit 1
22
+ fi
23
+
24
+ exit 0
@@ -0,0 +1,53 @@
1
+ # Windsurf Rules — webjs app
2
+
3
+ You are working on a webjs app — an AI-first, no-build, web-components-first
4
+ framework. Read AGENTS.md for the full API reference and CONVENTIONS.md for
5
+ project-specific conventions before writing any code.
6
+
7
+ ## Before starting ANY work
8
+
9
+ FIRST, before writing any code:
10
+ 1. Check `git branch --show-current`.
11
+ - If on main/master: create a feature branch before editing.
12
+ - If on a feature branch: verify it matches the current task.
13
+ 2. Sync: `git fetch origin && git rebase origin/main` if behind.
14
+
15
+ ## Autonomous mode (sandbox / no-prompt)
16
+
17
+ If running without interactive approval, auto-decide:
18
+ - On main? Auto-create feature/<task-slug> branch
19
+ - Parent behind? Auto-rebase. Merge? Auto-merge + delete feature branches.
20
+ - Auto-generate commit messages. Fix failing tests and violations.
21
+ Quality bar stays the same — no blocking on questions.
22
+
23
+ ## Mandatory workflow (never skip)
24
+
25
+ Every code change must include:
26
+ 1. Server tests in test/unit/*.test.ts (node:test)
27
+ 2. Browser tests in test/browser/*.test.js (WTR + Playwright, real Chromium)
28
+ 3. Documentation updates (AGENTS.md, docs/, website/ if they exist)
29
+ 4. Convention check: `npx webjs check` must pass
30
+
31
+ The user should never have to ask for tests or documentation.
32
+
33
+ ## Git rules
34
+
35
+ - COMMIT AND PUSH OFTEN. After each logical unit of work — don't accumulate
36
+ changes. Always `git push` after committing. This is automatic.
37
+ - Meaningful commit messages: what changed and why
38
+ - NEVER add Co-Authored-By or AI attribution trailers to commits
39
+ - Work on feature branches, never push directly to main
40
+ - Create pull requests for review
41
+ - NEVER merge any branch without explicit user permission. Always ask:
42
+ "Ready to merge <branch> into <target>? Delete or keep <branch> after?"
43
+ Wait for approval AND the delete/keep preference. Applies to ALL merges.
44
+ - Run `npx webjs test` before every commit
45
+
46
+ ## Framework specifics
47
+
48
+ - No build step: ES modules served directly
49
+ - Web components with shadow DOM by default
50
+ - One function per server action file (*.server.ts)
51
+ - Use webjs directives: classMap, styleMap, ref, when, choose, guard, etc.
52
+ - Use Context for cross-component data, Task for async data in components
53
+ - Full API reference in AGENTS.md