Skip to content

Commit 3ba33a3

Browse files
committed
foundation
1 parent 315c557 commit 3ba33a3

File tree

9 files changed

+522
-1
lines changed

9 files changed

+522
-1
lines changed

.augment/rules/ultracite.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Ultracite Code Standards
2+
3+
This project uses **Ultracite**, a zero-config Biome preset that enforces strict code quality standards through automated formatting and linting.
4+
5+
## Quick Reference
6+
7+
- **Format code**: `npx ultracite fix`
8+
- **Check for issues**: `npx ultracite check`
9+
- **Diagnose setup**: `npx ultracite doctor`
10+
11+
Biome (the underlying engine) provides extremely fast Rust-based linting and formatting. Most issues are automatically fixable.
12+
13+
---
14+
15+
## Core Principles
16+
17+
Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity.
18+
19+
### Type Safety & Explicitness
20+
21+
- Use explicit types for function parameters and return values when they enhance clarity
22+
- Prefer `unknown` over `any` when the type is genuinely unknown
23+
- Use const assertions (`as const`) for immutable values and literal types
24+
- Leverage TypeScript's type narrowing instead of type assertions
25+
- Use meaningful variable names instead of magic numbers - extract constants with descriptive names
26+
27+
### Modern JavaScript/TypeScript
28+
29+
- Use arrow functions for callbacks and short functions
30+
- Prefer `for...of` loops over `.forEach()` and indexed `for` loops
31+
- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access
32+
- Prefer template literals over string concatenation
33+
- Use destructuring for object and array assignments
34+
- Use `const` by default, `let` only when reassignment is needed, never `var`
35+
36+
### Async & Promises
37+
38+
- Always `await` promises in async functions - don't forget to use the return value
39+
- Use `async/await` syntax instead of promise chains for better readability
40+
- Handle errors appropriately in async code with try-catch blocks
41+
- Don't use async functions as Promise executors
42+
43+
### React & JSX
44+
45+
- Use function components over class components
46+
- Call hooks at the top level only, never conditionally
47+
- Specify all dependencies in hook dependency arrays correctly
48+
- Use the `key` prop for elements in iterables (prefer unique IDs over array indices)
49+
- Nest children between opening and closing tags instead of passing as props
50+
- Don't define components inside other components
51+
- Use semantic HTML and ARIA attributes for accessibility:
52+
- Provide meaningful alt text for images
53+
- Use proper heading hierarchy
54+
- Add labels for form inputs
55+
- Include keyboard event handlers alongside mouse events
56+
- Use semantic elements (`<button>`, `<nav>`, etc.) instead of divs with roles
57+
58+
### Error Handling & Debugging
59+
60+
- Remove `console.log`, `debugger`, and `alert` statements from production code
61+
- Throw `Error` objects with descriptive messages, not strings or other values
62+
- Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them
63+
- Prefer early returns over nested conditionals for error cases
64+
65+
### Code Organization
66+
67+
- Keep functions focused and under reasonable cognitive complexity limits
68+
- Extract complex conditions into well-named boolean variables
69+
- Use early returns to reduce nesting
70+
- Prefer simple conditionals over nested ternary operators
71+
- Group related code together and separate concerns
72+
73+
### Security
74+
75+
- Add `rel="noopener"` when using `target="_blank"` on links
76+
- Avoid `dangerouslySetInnerHTML` unless absolutely necessary
77+
- Don't use `eval()` or assign directly to `document.cookie`
78+
- Validate and sanitize user input
79+
80+
### Performance
81+
82+
- Avoid spread syntax in accumulators within loops
83+
- Use top-level regex literals instead of creating them in loops
84+
- Prefer specific imports over namespace imports
85+
- Avoid barrel files (index files that re-export everything)
86+
- Use proper image components (e.g., Next.js `<Image>`) over `<img>` tags
87+
88+
### Framework-Specific Guidance
89+
90+
**Next.js:**
91+
- Use Next.js `<Image>` component for images
92+
- Use `next/head` or App Router metadata API for head elements
93+
- Use Server Components for async data fetching instead of async Client Components
94+
95+
**React 19+:**
96+
- Use ref as a prop instead of `React.forwardRef`
97+
98+
**Solid/Svelte/Vue/Qwik:**
99+
- Use `class` and `for` attributes (not `className` or `htmlFor`)
100+
101+
---
102+
103+
## Testing
104+
105+
- Write assertions inside `it()` or `test()` blocks
106+
- Avoid done callbacks in async tests - use async/await instead
107+
- Don't use `.only` or `.skip` in committed code
108+
- Keep test suites reasonably flat - avoid excessive `describe` nesting
109+
110+
## When Biome Can't Help
111+
112+
Biome's linter will catch most issues automatically. Focus your attention on:
113+
114+
1. **Business logic correctness** - Biome can't validate your algorithms
115+
2. **Meaningful naming** - Use descriptive names for functions, variables, and types
116+
3. **Architecture decisions** - Component structure, data flow, and API design
117+
4. **Edge cases** - Handle boundary conditions and error states
118+
5. **User experience** - Accessibility, performance, and usability considerations
119+
6. **Documentation** - Add comments for complex logic, but prefer self-documenting code
120+
121+
---
122+
123+
Most formatting and common issues are automatically fixed by Biome. Run `npx ultracite fix` before committing to ensure compliance.

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Test
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
push:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Bun
19+
uses: oven-sh/setup-bun@v2
20+
with:
21+
bun-version: latest
22+
23+
- name: Install dependencies
24+
run: bun install
25+
26+
- name: Run tests
27+
run: bun test
28+
29+
- name: Run typecheck
30+
run: bun run typecheck
31+

.husky/pre-commit

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
bun test
2+
3+
#!/bin/sh
4+
# Exit on any error
5+
set -e
6+
7+
# Check if there are any staged files
8+
if [ -z "$(git diff --cached --name-only)" ]; then
9+
echo "No staged files to format"
10+
exit 0
11+
fi
12+
13+
# Store the hash of staged changes to detect modifications
14+
STAGED_HASH=$(git diff --cached | sha256sum | cut -d' ' -f1)
15+
16+
# Save list of staged files (handling all file states)
17+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
18+
PARTIALLY_STAGED=$(git diff --name-only)
19+
20+
# Stash unstaged changes to preserve working directory
21+
# --keep-index keeps staged changes in working tree
22+
git stash push --quiet --keep-index --message "pre-commit-stash" || true
23+
STASHED=$?
24+
25+
# Run formatter on the staged files
26+
bun x ultracite fix
27+
FORMAT_EXIT_CODE=$?
28+
29+
# Restore working directory state
30+
if [ $STASHED -eq 0 ]; then
31+
# Re-stage the formatted files
32+
if [ -n "$STAGED_FILES" ]; then
33+
echo "$STAGED_FILES" | while IFS= read -r file; do
34+
if [ -f "$file" ]; then
35+
git add "$file"
36+
fi
37+
done
38+
fi
39+
40+
# Restore unstaged changes
41+
git stash pop --quiet || true
42+
43+
# Restore partial staging if files were partially staged
44+
if [ -n "$PARTIALLY_STAGED" ]; then
45+
for file in $PARTIALLY_STAGED; do
46+
if [ -f "$file" ] && echo "$STAGED_FILES" | grep -q "^$file$"; then
47+
# File was partially staged - need to unstage the unstaged parts
48+
git restore --staged "$file" 2>/dev/null || true
49+
git add -p "$file" < /dev/null 2>/dev/null || git add "$file"
50+
fi
51+
done
52+
fi
53+
else
54+
# No stash was created, just re-add the formatted files
55+
if [ -n "$STAGED_FILES" ]; then
56+
echo "$STAGED_FILES" | while IFS= read -r file; do
57+
if [ -f "$file" ]; then
58+
git add "$file"
59+
fi
60+
done
61+
fi
62+
fi
63+
64+
# Check if staged files actually changed
65+
NEW_STAGED_HASH=$(git diff --cached | sha256sum | cut -d' ' -f1)
66+
if [ "$STAGED_HASH" != "$NEW_STAGED_HASH" ]; then
67+
echo "✨ Files formatted by Ultracite"
68+
fi
69+
70+
exit $FORMAT_EXIT_CODE

.vscode/settings.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"editor.defaultFormatter": "esbenp.prettier-vscode",
3+
"[javascript]": {
4+
"editor.defaultFormatter": "biomejs.biome"
5+
},
6+
"[typescript]": {
7+
"editor.defaultFormatter": "biomejs.biome"
8+
},
9+
"[javascriptreact]": {
10+
"editor.defaultFormatter": "biomejs.biome"
11+
},
12+
"[typescriptreact]": {
13+
"editor.defaultFormatter": "biomejs.biome"
14+
},
15+
"[json]": {
16+
"editor.defaultFormatter": "biomejs.biome"
17+
},
18+
"[jsonc]": {
19+
"editor.defaultFormatter": "biomejs.biome"
20+
},
21+
"[css]": {
22+
"editor.defaultFormatter": "biomejs.biome"
23+
},
24+
"[graphql]": {
25+
"editor.defaultFormatter": "biomejs.biome"
26+
},
27+
"typescript.tsdk": "node_modules/typescript/lib",
28+
"editor.formatOnSave": true,
29+
"editor.formatOnPaste": true,
30+
"emmet.showExpandedAbbreviation": "never",
31+
"editor.codeActionsOnSave": {
32+
"source.fixAll.biome": "explicit",
33+
"source.organizeImports.biome": "explicit"
34+
}
35+
}

action.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: 'Augment Action'
2+
description: 'GitHub Action that reacts to comments with emoji'
3+
author: 'Augment Code'
4+
branding:
5+
icon: 'eye'
6+
color: 'purple'
7+
8+
inputs:
9+
github_token:
10+
description: 'GitHub token for API access'
11+
required: true
12+
comment_id:
13+
description: 'The ID of the comment to react to'
14+
required: true
15+
event_name:
16+
description: 'The GitHub event name (pull_request_review_comment or issue_comment)'
17+
required: true
18+
19+
outputs:
20+
success:
21+
description: 'Whether the reaction was successfully added'
22+
value: ${{ steps.react.outputs.success }}
23+
24+
runs:
25+
using: 'composite'
26+
steps:
27+
- name: Setup Node
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: 22
31+
32+
- name: Install Action Dependencies
33+
run: npm install --production
34+
shell: bash
35+
working-directory: ${{ github.action_path }}
36+
37+
- name: React to comment
38+
id: react
39+
run: npx tsx ${{ github.action_path }}/src/index.ts
40+
shell: bash
41+
env:
42+
INPUT_GITHUB_TOKEN: ${{ inputs.github_token }}
43+
INPUT_COMMENT_ID: ${{ inputs.comment_id }}
44+
INPUT_EVENT_NAME: ${{ inputs.event_name }}
45+
GITHUB_REPOSITORY: ${{ github.repository }}
46+

index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
console.log("Hello via Bun!");
1+
console.log("Hello via Bun!");

src/index.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env node
2+
import * as core from "@actions/core";
3+
import { Octokit } from "@octokit/rest";
4+
import { getInput, parseRepository } from "./utils";
5+
6+
/**
7+
* Main function
8+
*/
9+
async function main(): Promise<void> {
10+
try {
11+
const githubToken = getInput("github_token", true);
12+
const commentIdStr = getInput("comment_id", true);
13+
const eventName = getInput("event_name", true);
14+
15+
// Validate comment ID
16+
const commentId = Number.parseInt(commentIdStr, 10);
17+
if (Number.isNaN(commentId)) {
18+
throw new Error(`Invalid comment_id: ${commentIdStr}. Must be a number.`);
19+
}
20+
21+
const { owner, repo } = parseRepository();
22+
23+
// Create Octokit instance
24+
const octokit = new Octokit({ auth: githubToken });
25+
26+
core.info(`🎯 Reacting to comment ${commentId} with :eyes:`);
27+
core.info(`📦 Repository: ${owner}/${repo}`);
28+
core.info(`📝 Event: ${eventName}`);
29+
30+
// React based on event type
31+
if (eventName === "pull_request_review_comment") {
32+
await octokit.rest.reactions.createForPullRequestReviewComment({
33+
owner,
34+
repo,
35+
comment_id: commentId,
36+
content: "eyes",
37+
});
38+
} else if (eventName === "issue_comment") {
39+
await octokit.rest.reactions.createForIssueComment({
40+
owner,
41+
repo,
42+
comment_id: commentId,
43+
content: "eyes",
44+
});
45+
} else {
46+
throw new Error(`Unsupported event type: ${eventName}`);
47+
}
48+
49+
core.info(`✅ Successfully added :eyes: reaction to comment ${commentId}`);
50+
core.setOutput("success", "true");
51+
} catch (error) {
52+
const errorMessage = error instanceof Error ? error.message : String(error);
53+
core.setFailed(errorMessage);
54+
core.setOutput("success", "false");
55+
}
56+
}
57+
58+
// Run the action
59+
main().catch((error) => {
60+
const errorMessage = error instanceof Error ? error.message : String(error);
61+
core.setFailed(`Unexpected error: ${errorMessage}`);
62+
});

0 commit comments

Comments
 (0)