diff --git a/.claude/README.md b/.claude/README.md
new file mode 100644
index 0000000000..7aa0bcf42b
--- /dev/null
+++ b/.claude/README.md
@@ -0,0 +1,49 @@
+# Contribution Guidelines
+
+This directory contains AI instructions extracted from RFC (Request for Comments) discussions that have been approved and landed in the project.
+
+## How It Works
+
+This directory is automatically maintained by the GitHub Actions workflow `.github/workflows/generate-claude-md.yml`. The workflow:
+
+1. Monitors discussions in the "Contribution RFC" category
+2. When a discussion receives the `RFC:Landed` label, its "## AI Instructions" section is extracted
+3. Creates/updates a markdown file in this directory with the extracted content
+4. Generates the root `CLAUDE.md` file with links to all RFC files
+5. Creates a PR for human review
+
+## File Naming Convention
+
+RFC files are named based on the discussion title:
+- Convert to lowercase
+- Replace spaces with hyphens
+- Remove special characters
+
+Example: "Testing Strategy" → `testing-strategy.md`
+
+## Structure
+
+Each RFC file contains:
+- Title of the RFC as a header
+- AI Instructions extracted from the discussion
+
+## Adding New RFCs
+
+To add a new RFC to this system:
+
+1. Create or edit a discussion in the "Contribution RFC" category
+2. Add a section titled `## AI Instructions` with the guidance for AI coding assistants
+3. Add the `RFC:Landed` label to the discussion
+4. The workflow will automatically create a PR with the changes
+
+## Removing RFCs
+
+To remove an RFC:
+1. Remove the `RFC:Landed` label from the discussion
+2. The workflow will automatically create a PR that removes the file
+
+## Manual Updates
+
+While files are auto-generated, if you need to make manual corrections:
+1. Edit the source discussion on GitHub
+2. The workflow will regenerate the files on the next trigger
diff --git a/.claude/instructions/setup-function-instead-of-beforeeach-in-unit-service-level-tests.md b/.claude/instructions/setup-function-instead-of-beforeeach-in-unit-service-level-tests.md
new file mode 100644
index 0000000000..30e371f941
--- /dev/null
+++ b/.claude/instructions/setup-function-instead-of-beforeeach-in-unit-service-level-tests.md
@@ -0,0 +1,51 @@
+---
+description:
+globs: **/*.spec.tsx,**/*.spec.ts
+alwaysApply: false
+---
+## Use setup function instead of beforeEach
+
+## Description
+- Use `setup` function instead of `beforeEach`
+- `setup` function must be at the bottom of the root `describe` block
+- `setup` function creates an object under test and returns it
+- `setup` function should accept a single parameter with inline type definition
+- Don't use shared state in `setup` function
+- Don't specify return type of `setup` function
+
+## Examples
+
+### Good
+```typescript
+describe("UserProfile", () => {
+ it("renders user name when provided", () => {
+ setup({ name: "John Doe" });
+ expect(screen.queryByText("John Doe")).toBeInTheDocument();
+ });
+
+ function setup(input: { name?: string; email?: string; isLoading?: boolean; error?: string }) {
+ render();
+ return input;
+ }
+});
+```
+
+### Bad
+```typescript
+describe("UserProfile", () => {
+ let props: UserProfileProps;
+
+ beforeEach(() => {
+ props = { name: "John Doe" };
+ render();
+ });
+
+ it("renders user name when provided", () => {
+ expect(screen.getByText("John Doe")).toBeInTheDocument();
+ });
+});
+```
+
+## References
+- [DeploymentName.spec.tsx](mdc:apps/deploy-web/src/components/deployments/DeploymentName/DeploymentName.spec.tsx)
+- https://github.com/akash-network/console/discussions/910
\ No newline at end of file
diff --git a/.github/workflows/generate-claude-md.yml b/.github/workflows/generate-claude-md.yml
new file mode 100644
index 0000000000..5204f08441
--- /dev/null
+++ b/.github/workflows/generate-claude-md.yml
@@ -0,0 +1,106 @@
+name: Generate CLAUDE.md
+
+on:
+ discussion:
+ types: [labeled, unlabeled, edited]
+
+concurrency:
+ group: ${{ github.workflow }}
+ cancel-in-progress: true
+
+permissions:
+ contents: write
+ pull-requests: write
+
+env:
+ CONTRIBUTION_BRANCH: automated/claude-md-update
+
+jobs:
+ generate:
+ name: Generate CLAUDE.md
+ if: github.event.discussion.category.name == 'Contribution RFC'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ with:
+ ref: main
+
+ - name: Setup Node.js
+ uses: volta-cli/action@v4
+
+ - name: Generate CLAUDE.md
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ run: |
+ node --experimental-strip-types --no-warnings script/generate-claude-md.ts
+
+ - name: Check for Changes
+ id: check-changes
+ run: |
+ if git diff --quiet CLAUDE.md .claude/instructions/; then
+ echo "has_changes=false" >> "$GITHUB_OUTPUT"
+ echo "No changes detected"
+ else
+ echo "has_changes=true" >> "$GITHUB_OUTPUT"
+ echo "Changes detected"
+ fi
+
+ - name: Configure Git
+ if: steps.check-changes.outputs.has_changes == 'true'
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ - name: Commit and Push Changes
+ if: steps.check-changes.outputs.has_changes == 'true'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # Create or switch to the branch
+ git fetch origin "${{ env.CONTRIBUTION_BRANCH }}" 2>/dev/null || true
+ git checkout -B "${{ env.CONTRIBUTION_BRANCH }}"
+
+ # Stage and commit changes
+ git add CLAUDE.md .claude/instructions/
+ git commit -m "chore: update CLAUDE.md from RFC discussions" \
+ -m "Discussion #${{ github.event.discussion.number }} was ${{ github.event.action }}"
+
+ # Push changes
+ git push -f origin "${{ env.CONTRIBUTION_BRANCH }}"
+
+ - name: Create or Update Pull Request
+ if: steps.check-changes.outputs.has_changes == 'true'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ # Check if PR already exists
+ PR_NUMBER=$(gh pr list \
+ --base main \
+ --head "${{ env.CONTRIBUTION_BRANCH }}" \
+ --state open \
+ --limit 1 \
+ --json number \
+ --jq ".[0].number" || echo "")
+
+ if [ -z "$PR_NUMBER" ]; then
+ echo "Creating new PR..."
+
+ # Create PR body
+ printf "This PR automatically updates CLAUDE.md and contribution guidelines based on RFC discussions.\n\n" > /tmp/pr-body.md
+ printf "Triggered by: Discussion #%s (%s)\n\n" "${{ github.event.discussion.number }}" "${{ github.event.action }}" >> /tmp/pr-body.md
+ printf "Changes:\n- Generated/updated CLAUDE.md\n- Updated RFC files in .contribution-guidelines/\n\n" >> /tmp/pr-body.md
+ printf "Review Notes:\n- Verify that the extracted AI Instructions are correct\n" >> /tmp/pr-body.md
+ printf -- "- Check that all RFC:Landed discussions are included\n" >> /tmp/pr-body.md
+ printf -- "- Ensure removed RFCs are properly cleaned up\n" >> /tmp/pr-body.md
+
+ gh pr create \
+ --base main \
+ --head "${{ env.CONTRIBUTION_BRANCH }}" \
+ --title "chore: update CLAUDE.md from RFC discussions" \
+ --body-file /tmp/pr-body.md
+ else
+ echo "PR #$PR_NUMBER already exists and has been updated"
+ fi
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..8266689977
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,9 @@
+# Contribution Guidelines
+
+This file aggregates all RFC (Request for Comments) contribution guidelines that have landed.
+
+
+## `setup` function instead of `beforeEach` in unit & service level tests
+
+See detailed guidelines:
+@./.claude/instructions/setup-function-instead-of-beforeeach-in-unit-service-level-tests.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2c8d1a99d0..03790b6ce7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,4 +64,20 @@ If you're ready to contribute, follow our guidelines:
Note that this process allows multiple developers to collaborate effectively and maintain high-quality code for a long-lasting entity; the project.
+### AI Coding Guidelines
+
+This repository uses AI-assisted coding tools (GitHub Copilot, Cursor, Claude, etc.). To help these tools generate better code:
+
+- **CLAUDE.md**: Contains aggregated contribution guidelines extracted from approved RFCs
+- **Contribution RFCs**: Guidelines are sourced from discussions in the [Contribution RFC category](https://github.com/akash-network/console/discussions/categories/contribution-rfc)
+- **Auto-Generated**: The CLAUDE.md file is automatically generated when RFC discussions receive the `RFC:Landed` label
+
+To contribute new AI guidelines:
+1. Create a discussion in the "Contribution RFC" category
+2. Include a section titled `## AI Instructions` with guidance for AI coding assistants
+3. Once approved, add the `RFC:Landed` label
+4. The guidelines will be automatically added to CLAUDE.md
+
+See [`.claude/README.md`](./.claude/README.md) for more details.
+
*That your commitment to following these specs will make a difference.*
\ No newline at end of file
diff --git a/script/generate-claude-md.ts b/script/generate-claude-md.ts
new file mode 100755
index 0000000000..6b38ef696e
--- /dev/null
+++ b/script/generate-claude-md.ts
@@ -0,0 +1,235 @@
+#!/usr/bin/env node
+
+import { execSync } from "child_process";
+import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from "fs";
+import { join } from "path";
+
+// Configuration
+const REPO = process.env.GITHUB_REPOSITORY || "akash-network/console";
+const CATEGORY_NAME = "Contribution RFC";
+const LABEL_NAME = "RFC:Landed";
+const GUIDELINES_DIR = ".claude/instructions";
+const OUTPUT_FILE = "CLAUDE.md";
+
+// Colors for output
+const colors = {
+ red: "\x1b[0;31m",
+ green: "\x1b[0;32m",
+ yellow: "\x1b[1;33m",
+ reset: "\x1b[0m"
+};
+
+function log(level: "INFO" | "WARN" | "ERROR", message: string): void {
+ const color = level === "INFO" ? colors.green : level === "WARN" ? colors.yellow : colors.red;
+ console.log(`${color}[${level}]${colors.reset} ${message}`);
+}
+
+// Convert RFC title to filename
+function titleToFilename(title: string): string {
+ return title
+ .toLowerCase()
+ .replace(/[^a-z0-9 -]/g, "")
+ .replace(/\s+/g, "-")
+ .replace(/-+/g, "-")
+ .replace(/^-|-$/g, "");
+}
+
+// Extract AI Instructions section from discussion body
+function extractAIInstructions(body: string): string | null {
+ const lines = body.split("\n");
+ let found = false;
+ const content: string[] = [];
+
+ for (const line of lines) {
+ if (line.match(/^##\s*AI\s*Instructions?/)) {
+ found = true;
+ continue;
+ }
+ if (found && (/^/.test(line) || line.startsWith("## "))) {
+ break;
+ }
+ if (found) {
+ // reduce heading level by 1
+ content.push(line.startsWith("#") ? line.slice(1) : line);
+ }
+ }
+
+ const result = content.join("\n").trim();
+ return result || null;
+}
+
+// Execute gh CLI command
+function ghCommand(args: string[]): string {
+ try {
+ return execSync(`gh ${args.join(" ")}`, {
+ encoding: "utf-8",
+ stdio: ["pipe", "pipe", "pipe"]
+ }).trim();
+ } catch (error: any) {
+ log("ERROR", `gh command failed: ${error.message}`);
+ throw error;
+ }
+}
+
+interface Discussion {
+ title: string;
+ body: string;
+ labels: { nodes: Array<{ name: string }> };
+}
+
+interface DiscussionsResponse {
+ data: {
+ repository: {
+ discussionCategories: {
+ nodes: Array<{ id: string; name: string }>;
+ };
+ discussions: {
+ nodes: Discussion[];
+ };
+ };
+ };
+}
+
+function main(): void {
+ log("INFO", "Starting CLAUDE.md generation...");
+
+ // Check if gh CLI is available
+ try {
+ execSync("gh --version", { stdio: "ignore" });
+ } catch {
+ log("ERROR", "gh CLI is not installed. Please install it first.");
+ process.exit(1);
+ }
+
+ // Create guidelines directory if it doesn't exist
+ if (!existsSync(GUIDELINES_DIR)) {
+ mkdirSync(GUIDELINES_DIR, { recursive: true });
+ }
+
+ const [owner, repo] = REPO.split("/");
+
+ // Get the category ID for "Contribution RFC"
+ log("INFO", `Fetching category ID for '${CATEGORY_NAME}'...`);
+
+ const categoryQuery = gql`
+ query ($owner: String!, $repo: String!) {
+ repository(owner: $owner, name: $repo) {
+ discussionCategories(first: 100) {
+ nodes {
+ id
+ name
+ }
+ }
+ }
+ }
+ `;
+
+ const categoryResult: DiscussionsResponse = JSON.parse(
+ ghCommand(["api", "graphql", "-f", `query='${categoryQuery}'`, "-F", `owner=${owner}`, "-F", `repo=${repo}`])
+ );
+
+ const category = categoryResult.data.repository.discussionCategories.nodes.find(c => c.name === CATEGORY_NAME);
+
+ if (!category) {
+ log("ERROR", `Could not find category '${CATEGORY_NAME}'`);
+ process.exit(1);
+ }
+
+ log("INFO", `Found category ID: ${category.id}`);
+
+ // Fetch all discussions with RFC:Landed label
+ log("INFO", `Fetching discussions with label '${LABEL_NAME}'...`);
+
+ const discussionsQuery = gql`
+ query ($owner: String!, $repo: String!, $categoryId: ID!) {
+ repository(owner: $owner, name: $repo) {
+ discussions(first: 100, categoryId: $categoryId) {
+ nodes {
+ title
+ body
+ labels(first: 10) {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ `;
+
+ const discussionsResult: DiscussionsResponse = JSON.parse(
+ ghCommand(["api", "graphql", "-f", `query='${discussionsQuery}'`, "-F", `owner=${owner}`, "-F", `repo=${repo}`, "-F", `categoryId=${category.id}`])
+ );
+
+ const discussions = discussionsResult.data.repository.discussions.nodes || [];
+
+ // Track existing files to clean up removed RFCs
+ const existingFiles = existsSync(GUIDELINES_DIR)
+ ? readdirSync(GUIDELINES_DIR)
+ .filter(f => f.endsWith(".md") && f !== "README.md")
+ .map(f => join(GUIDELINES_DIR, f))
+ : [];
+
+ // Process each discussion
+ log("INFO", "Processing discussions...");
+ const processedFiles: string[] = [];
+ let claudeContent = "# Contribution Guidelines\n\nThis file aggregates all RFC (Request for Comments) contribution guidelines that have landed.\n\n";
+ let rfcCount = 0;
+
+ for (const discussion of discussions) {
+ const hasLabel = discussion.labels.nodes.some(l => l.name === LABEL_NAME);
+
+ if (hasLabel) {
+ log("INFO", `Processing RFC: ${discussion.title}`);
+
+ // Extract AI Instructions section
+ const aiInstructions = extractAIInstructions(discussion.body);
+
+ if (!aiInstructions) {
+ log("WARN", `No '## AI Instructions' section found in '${discussion.title}'. Skipping...`);
+ continue;
+ }
+
+ // Generate filename
+ const filename = `${titleToFilename(discussion.title)}.md`;
+ const filepath = join(GUIDELINES_DIR, filename);
+ processedFiles.push(filepath);
+
+ writeAiInstruction(filepath, aiInstructions);
+
+ log("INFO", `Created/Updated: ${filepath}`);
+
+ // Add to CLAUDE.md content
+ claudeContent += `\n## ${discussion.title}\n\nSee detailed guidelines:\n@./${GUIDELINES_DIR}/${filename}\n`;
+ rfcCount++;
+ }
+ }
+
+ // Remove files for RFCs that no longer have RFC:Landed label
+ for (const existingFile of existingFiles) {
+ if (!processedFiles.includes(existingFile)) {
+ log("INFO", `Removing obsolete file: ${existingFile}`);
+ unlinkSync(existingFile);
+ }
+ }
+
+ // Generate root CLAUDE.md
+ log("INFO", `Generating root ${OUTPUT_FILE}...`);
+ writeFileSync(OUTPUT_FILE, claudeContent, "utf-8");
+
+ log("INFO", `Successfully generated ${OUTPUT_FILE} with ${rfcCount} RFC(s)`);
+}
+
+function gql(strings: TemplateStringsArray, ...values: string[]): string {
+ return strings
+ .reduce((acc, str, i) => acc + str + (values[i] || ""), "")
+ .replace(/\s+/g, " ")
+ .trim();
+}
+
+function writeAiInstruction(filepath: string, aiInstructions: string): void {
+ writeFileSync(filepath, aiInstructions, "utf-8");
+}
+
+main();