Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": 1,
"hooks": {
"stop": [
{
"command": "npx tsx .cursor/hooks/run-phpstan-on-stop.ts"
}
]
}
}
90 changes: 90 additions & 0 deletions .cursor/hooks/run-phpstan-on-stop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/// <reference types="node" />
import { spawnSync } from "node:child_process";

type StopHookInput = {
status?: "completed" | "aborted" | "error";
};

const COMMAND = {
tool: "phpstan",
cmd: "./vendor/bin/phpstan analyse --memory-limit=512M",
};

async function parseInput(): Promise<StopHookInput> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) {
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
}
const text = Buffer.concat(chunks).toString("utf8");
try {
return JSON.parse(text) as StopHookInput;
} catch {
return {};
}
}

function hasGitChanges(): boolean {
const result = spawnSync("git", [
"status",
"--porcelain",
"--untracked-files",
]);
return result.stdout.toString().trim().length > 0;
}

function runCommand(
_name: string,
cmd: string,
): { ok: boolean; output: string } {
const result = spawnSync(cmd, {
stdio: ["inherit", "pipe", "pipe"],
shell: true,
});
const out = result.stdout?.toString() ?? "";
const err = result.stderr?.toString() ?? "";
const output = [out, err].filter(Boolean).join("\n").trim();
if (output) process.stderr.write(output);
return { ok: result.status === 0, output };
}

async function main(): Promise<void> {
const input = await parseInput();

if (input.status !== "completed") {
process.stdout.write(JSON.stringify({}) + "\n");
return;
}

if (!hasGitChanges()) {
process.stdout.write(JSON.stringify({}) + "\n");
return;
}

const MAX_ERROR_CHARS = 1000;

const { ok, output } = runCommand(COMMAND.tool, COMMAND.cmd);
const failures = ok ? [] : [{ tool: COMMAND.tool, output }];

const truncatedOutput =
output && output.length > MAX_ERROR_CHARS
? output.slice(0, MAX_ERROR_CHARS) + "\n… (truncated)"
: output || "(no output)";

const result =
failures.length > 0
? {
followup_message: [
`Please fix the errors reported by ${COMMAND.tool}.`,
"Don't do any other investigation.",
"",
`<${COMMAND.tool}-errors>\n${truncatedOutput}\n</${COMMAND.tool}-errors>`,
].join("\n"),
}
: {};
process.stdout.write(JSON.stringify(result) + "\n");
}

main().catch((err) => {
console.error("[run-phpstan-on-stop]", err);
process.stdout.write(JSON.stringify({}) + "\n");
});
11 changes: 11 additions & 0 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mcpServers": {
"laravel-boost": {
"command": "php",
"args": [
"artisan",
"boost:mcp"
]
}
}
}
12 changes: 12 additions & 0 deletions .cursor/rules/code-formatters.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
alwaysApply: true
---

# Code formatters & refactors (auto-updating tools)

These tools modify code automatically. Run them before finalizing changes so the codebase stays consistent.

## Rector

- Run `vendor/bin/rector --ansi` before finalizing changes so refactoring rules are applied.
- Rector updates code on its own; run it to keep the codebase aligned with configured rules.
66 changes: 66 additions & 0 deletions .cursor/rules/conventional-commits.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
description: Use Conventional Commits format for all commit messages
alwaysApply: true
---

# Conventional Commits

## Format

```
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

## Types

| Type | Use when |
|------|----------|
| `feat` | Adding a new feature (MINOR in SemVer) |
| `fix` | Patching a bug (PATCH in SemVer) |
| `docs` | Documentation only |
| `style` | Formatting, whitespace, no code change |
| `refactor` | Code change that neither fixes a bug nor adds a feature |
| `perf` | Performance improvement |
| `test` | Adding or updating tests |
| `build` | Build system or dependencies |
| `ci` | CI configuration |
| `chore` | Other changes (maintenance, tooling) |

## Scope

Optional. Use a noun describing the affected area in parentheses: `feat(auth):`, `fix(api):`.

## Examples

```
feat: add user registration endpoint
```

```
fix(login): prevent redirect loop on session expiry
```

```
chore(hooks): rename run-quality-checks to run-phpstan-on-stop
```

```
docs: correct spelling of CHANGELOG
```

```
feat(api)!: drop support for deprecated endpoints

BREAKING CHANGE: /v1/users has been removed. Use /v2/users instead.
```

## Rules

- Description is required and MUST immediately follow the type/scope.
- Use imperative mood: "add feature" not "added feature".
- No period at the end of the description.
- Use `!` after type/scope or `BREAKING CHANGE:` in footer for breaking changes.
Loading