[AI] Consolidate agent tooling and simplify developer guidance (#8089)

* [AI] Replace mechanical agent rules with shared agent hooks + nano-staged

Move the mechanical "remember to run X" rules out of AGENTS.md and the
agent guidance docs into deterministic, cross-agent hooks, and migrate
the pre-commit runner from lint-staged to nano-staged.

- Add shared hook scripts in scripts/agent-hooks/ (git-guard,
  format-edited-file, no-strict-ignore-new-file, prefer-one-component,
  check-on-stop, common helpers) wired for Claude (.claude/settings.json),
  Codex (.codex/config.toml), and Cursor (.cursor/hooks.json + adapters).
- Enforce "avoid enum" via a new actual/no-enum lint rule, grandfathering
  the two existing declarations in .oxlintrc.json overrides.
- Migrate lint-staged -> nano-staged (.nano-staged.json, .husky/pre-commit,
  package.json).
- Trim AGENTS.md, .github/agents/pr-and-commit-rules.md, the Cursor rule,
  and the committing skill down to the rules that aren't auto-enforced.
- Add release note 8089.md.

* [AI] Align PR-title wording in AGENTS.md with canonical rules

* [AI] Fix check-on-stop hook on bash 3.2 and TSV column collapse

The Stop hook silently no-op'd in two ways:

- `declare -A` isn't supported on stock macOS bash 3.2, and the
  slash/hyphen path subscript aborted under `set -u`. Dedupe via a
  space-padded string match instead.
- jq emitted an empty middle TSV field for a package with test but no
  typecheck; tab is IFS-whitespace so `read` collapsed it and shifted the
  columns (eslint-plugin-actual then ran neither). Emit non-empty
  sentinels (-, yes/no) so no field can collapse.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
This commit is contained in:
Matiss Janis Aboltins
2026-06-08 20:54:54 +00:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 27c6ded5a9
commit 328417f8de
23 changed files with 633 additions and 373 deletions
+50
View File
@@ -0,0 +1,50 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/scripts/agent-hooks/format-edited-file.sh\""
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/scripts/agent-hooks/prefer-one-component.sh\""
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/scripts/agent-hooks/git-guard.sh\""
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/scripts/agent-hooks/no-strict-ignore-new-file.sh\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/scripts/agent-hooks/check-on-stop.sh\"",
"timeout": 600
}
]
}
]
}
}
@@ -9,19 +9,19 @@ Before creating any git commit or pull request in this repo, read:
**`.github/agents/pr-and-commit-rules.md`**
It is the authoritative source for the `[AI]` prefix requirement, the git-safety rules, the pre-commit quality checklist (`yarn typecheck`, `yarn lint:fix`, relevant tests, translated strings), the rule about leaving the PR template blank, and the quick-reference workflow. Read it on every commit/PR session so any updates to those rules are picked up automatically.
It is the authoritative source for the rules you must apply yourself: the `[AI]` PR-title prefix and leaving the PR template blank (and the Chinese exception). The mechanical commit/git rules are enforced by tooling and aren't restated there. Read it on every commit/PR session so any updates are picked up automatically.
## The one rule worth restating
Every commit message and every pull request title MUST begin with `[AI]`. This is the single most-violated rule and the cost of forgetting it is high — the commit has to be amended or the PR has to be renamed by hand. Examples:
Every commit message and every pull request title MUST begin with `[AI]`. Commit messages are checked by the git-guard hook (a missing prefix is blocked before the commit lands), so the one that needs _your_ attention is the PR title — nothing checks it for you, and the cost of forgetting it is high since the PR has to be renamed by hand. Examples:
- `[AI] Fix type error in account validation` — correct
- `Fix type error in account validation` — wrong, missing prefix
Everything else (git safety, hooks, label, PR template, pre-commit checklist) lives in the rules file. Read it before each commit/PR rather than relying on this skill's summary, because the rules file evolves and this skill deliberately does not restate it.
The PR-template rule and the rest live in the rules file. Read it before each commit/PR rather than relying on this skill's summary, because the rules file evolves and this skill deliberately does not restate it.
## Why this matters
Maintainers triage AI-authored contributions separately, and the `[AI]` prefix is what makes that triage fast — it also drives the automatic `"AI generated"` PR label, so getting the prefix right is the one action that gates the whole triage path. Without it the PR looks like a normal human contribution and the wrong review process gets applied. The "do not fill in the PR template" rule exists because the human is the one who actually tested the change and can write the Description / Testing / Checklist sections honestly — an AI-filled template misrepresents who did what. The hook-skipping prohibition (`--no-verify`, `--no-gpg-sign`) exists because this codebase enforces formatting and lint at commit time deliberately; bypassing the hook is how broken code lands on a branch.
Maintainers triage AI-authored contributions separately, and the `[AI]` prefix is what makes that triage fast — it also drives the automatic `"AI generated"` PR label, so getting the prefix right is the one action that gates the whole triage path. Without it the PR looks like a normal human contribution and the wrong review process gets applied. The "do not fill in the PR template" rule exists because the human is the one who actually tested the change and can write the Description / Testing / Checklist sections honestly — an AI-filled template misrepresents who did what.
The rules file is short. Read it.
+51
View File
@@ -0,0 +1,51 @@
# Codex project hooks for actualbudget/actual.
#
# Codex discovers project config by walking up to the .git root, so this file
# applies to the whole repo. The hook scripts are shared with Claude Code and
# Cursor (see scripts/agent-hooks/). Codex's Bash hook is Claude-compatible
# (tool_input.command), so the git guard is reused directly; exit code 2 + stderr
# blocks the call.
#
# Known limitation: Codex's `apply_patch` PreToolUse payload carries the diff as a
# raw string in tool_input.command (no tool_input.file_path / .content), so the
# file-content hooks below (strict-ignore, format, one-component) only fire for
# the Write/Edit tools and no-op for apply_patch. They never false-block. The
# Bash/git guard and the Stop check work regardless. The file hooks are fully
# effective under Claude Code and Cursor.
#
# Non-managed hooks must be trusted once via `/hooks` in the Codex CLI.
# Block unsafe git/workspace commands ([AI] prefix, --no-verify, force-push,
# push-to-main, git config writes, yarn-from-a-workspace).
[[hooks.PreToolUse]]
matcher = "^Bash$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = '"$(git rev-parse --show-toplevel)/scripts/agent-hooks/git-guard.sh"'
statusMessage = "Checking shell command against repo policy"
# Block creating a new file that contains // @ts-strict-ignore.
[[hooks.PreToolUse]]
matcher = "^apply_patch$|^Write$|^Edit$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = '"$(git rev-parse --show-toplevel)/scripts/agent-hooks/no-strict-ignore-new-file.sh"'
statusMessage = "Checking new file is type-strict"
# Format/lint the edited file, and warn on multiple components in a new .tsx.
[[hooks.PostToolUse]]
matcher = "^apply_patch$|^Write$|^Edit$"
[[hooks.PostToolUse.hooks]]
type = "command"
command = '"$(git rev-parse --show-toplevel)/scripts/agent-hooks/format-edited-file.sh"'
[[hooks.PostToolUse.hooks]]
type = "command"
command = '"$(git rev-parse --show-toplevel)/scripts/agent-hooks/prefer-one-component.sh"'
# When the turn ends, typecheck + test the workspaces this branch touched.
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = '"$(git rev-parse --show-toplevel)/scripts/agent-hooks/check-on-stop.sh"'
timeout = 600
statusMessage = "Typecheck + test touched workspaces"
+7
View File
@@ -0,0 +1,7 @@
{
"version": 1,
"hooks": {
"beforeShellExecution": [{ "command": "./.cursor/hooks/guard-shell.sh" }],
"afterFileEdit": [{ "command": "./.cursor/hooks/after-file-edit.sh" }]
}
}
+38
View File
@@ -0,0 +1,38 @@
#!/bin/sh
# Cursor `afterFileEdit` adapter.
#
# afterFileEdit is informational-only: it can format the edited file (a side
# effect on disk) and PRINT advisories to Cursor's Hooks output channel, but it
# cannot block or message the agent. So under Cursor the new-file rules
# (no @ts-strict-ignore, one component per file) are advisory, not enforced.
# Cursor input on stdin: { file_path, edits, ... }.
input=$(cat)
file=$(printf '%s' "$input" | jq -r '.file_path // empty')
[ -n "$file" ] && [ -f "$file" ] || exit 0
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
payload=$(jq -n --arg f "$file" '{tool_input:{file_path:$f}}')
# Format/lint the edited file (side effect on disk).
printf '%s' "$payload" | "$ROOT/scripts/agent-hooks/format-edited-file.sh" >/dev/null 2>&1
dir=$(dirname "$file")
# Normalize to a repo-root-relative path so the git pathspec is unambiguous
# whether Cursor passed an absolute or relative file_path.
abs=$(CDPATH= cd "$dir" 2>/dev/null && pwd)/$(basename "$file")
rel=${abs#"$ROOT"/}
is_new() { ! git -C "$ROOT" ls-files --error-unmatch "$rel" >/dev/null 2>&1; }
# Advisory: new file that is not type-strict.
case "$file" in
*.ts | *.tsx)
if is_new && grep -q "@ts-strict-ignore" "$file" 2>/dev/null; then
echo "[actual] Advisory: new file '$file' uses // @ts-strict-ignore — new files should be type-strict (AGENTS.md)."
fi ;;
esac
# Advisory: new .tsx with multiple components (reuse the shared check).
msg=$(printf '%s' "$payload" | "$ROOT/scripts/agent-hooks/prefer-one-component.sh" 2>&1)
[ -n "$msg" ] && echo "[actual] Advisory: $msg"
exit 0
+46
View File
@@ -0,0 +1,46 @@
#!/bin/sh
# Cursor `beforeShellExecution` adapter.
#
# Translates Cursor's hook I/O to the shared guard in
# scripts/agent-hooks/git-guard.sh. Cursor input on stdin: { command, cwd, ... }.
# Output on stdout: { permission: "allow" | "deny", userMessage, agentMessage }.
input=$(cat)
ROOT=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
deny() {
jq -n --arg m "$1" \
'{permission:"deny", userMessage:"Blocked by repo policy", agentMessage:$m}'
exit 0
}
allow() {
jq -n '{permission:"allow"}'
exit 0
}
# Fail closed: a malformed payload (jq parse failure / missing command) must
# deny, not silently allow.
cmd=$(printf '%s' "$input" | jq -re '.command // empty') ||
deny "Invalid Cursor hook payload: could not read .command."
cwd=$(printf '%s' "$input" | jq -r '.cwd // ""')
# Cursor passes cwd rather than a literal `cd packages/...`, so catch
# yarn-run-from-a-workspace here before delegating.
case "$cwd" in
"$ROOT"/packages | "$ROOT"/packages/*)
case "$cmd" in
yarn* | *" yarn "*)
deny "Run yarn from the repo root, not a child workspace (AGENTS.md). Use 'yarn workspace <name> <cmd>' from $ROOT." ;;
esac ;;
esac
# Delegate the git-safety checks to the shared guard (reads .tool_input.command).
# Fail closed: deny on a block (exit 2) AND on any unexpected guard failure.
status=0
err=$(printf '{"tool_input":{"command":%s}}' "$(jq -n --arg c "$cmd" '$c')" |
"$ROOT/scripts/agent-hooks/git-guard.sh" 2>&1) || status=$?
case "$status" in
0) allow ;;
2) deny "$err" ;;
*) deny "git-guard failed (exit $status): $err" ;;
esac
+8 -63
View File
@@ -6,69 +6,14 @@ alwaysApply: true
# PR and Commit Rules for AI Agents
Canonical source: `.github/agents/pr-and-commit-rules.md`
## Commit Rules
### [AI] Prefix Requirement
**ALL commit messages MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
- `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
### Git Safety Rules
- **Never** update git config
- **Never** run destructive git operations (force push, hard reset) unless the user explicitly requests it
- **Never** skip hooks (`--no-verify`, `--no-gpg-sign`)
- **Never** force push to `main`/`master`
- **Never** commit unless explicitly asked by the user
## Pre-Commit Quality Checklist
Before committing, ensure all of the following:
- [ ] Commit message is prefixed with `[AI]`
- [ ] `yarn typecheck` passes
- [ ] `yarn lint:fix` has been run
- [ ] Relevant tests pass
- [ ] User-facing strings are translated
- [ ] Code style conventions followed (see `AGENTS.md` for full style guide)
Canonical source: `.github/agents/pr-and-commit-rules.md`. The mechanical commit
and git rules are handled by tooling; only the rules you must apply yourself are
listed here.
## Pull Request Rules
### [AI] Prefix Requirement
**ALL pull request titles MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
### Labels
Add the **"AI generated"** label to all AI-created pull requests.
### PR Template: Do Not Fill In
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is.
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese** (简体中文).
## Quick-Reference Workflow
1. Make your changes
2. Run `yarn typecheck` — fix any errors
3. Run `yarn lint:fix` — fix any remaining lint errors
4. Run relevant tests (`yarn test` for all, or workspace-specific)
5. Stage files and commit with `[AI]` prefix — do not skip hooks
6. When creating a PR:
- Use `[AI]` prefix in the title
- Add the `"AI generated"` label
- Leave the PR template blank (do not fill it in)
- **ALL pull request titles MUST be prefixed with `[AI]`.** This isn't enforced
automatically — you have to apply it yourself.
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`) — leave it
blank for the human. Exception: if a human explicitly asks, fill it out **in
Chinese** (简体中文).
+12 -55
View File
@@ -1,67 +1,24 @@
# PR and Commit Rules for AI Agents
This is the single source of truth for all commit and pull request rules that AI agents must follow when working with Actual Budget.
## Commit Rules
### [AI] Prefix Requirement
**ALL commit messages MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
- `Add support for new transaction categories` (MISSING PREFIX - NOT ALLOWED)
### Git Safety Rules
- **Never** update git config
- **Never** run destructive git operations (force push, hard reset) unless the user explicitly requests it
- **Never** skip hooks (`--no-verify`, `--no-gpg-sign`)
- **Never** force push to `main`/`master`
- **Never** commit unless explicitly asked by the user
## Pre-Commit Quality Checklist
Before committing, ensure all of the following:
- [ ] Commit message is prefixed with `[AI]`
- [ ] `yarn typecheck` passes
- [ ] `yarn lint:fix` has been run
- [ ] Relevant tests pass
- [ ] User-facing strings are translated
- [ ] Code style conventions followed (see `AGENTS.md` for full style guide)
The mechanical commit and git rules are handled by tooling. This file lists only
the rules you have to apply yourself.
## Pull Request Rules
### [AI] Prefix Requirement
### `[AI]` prefix on PR titles
**ALL pull request titles MUST be prefixed with `[AI]`.** This is a mandatory requirement with no exceptions.
**ALL pull request titles MUST be prefixed with `[AI]`.** This isn't enforced
automatically — you have to apply it yourself.
**Examples:**
- `[AI] Fix type error in account validation`
- `[AI] Add support for new transaction categories`
- `Fix type error in account validation` (MISSING PREFIX - NOT ALLOWED)
- `Fix type error in account validation` (MISSING PREFIX — NOT ALLOWED)
The `"AI generated"` label is applied automatically to any PR whose title starts with `[AI]`, so there is nothing extra to do — and outside contributors cannot apply labels anyway.
### Do not fill in the PR template
### PR Template: Do Not Fill In
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all blank spaces and placeholder comments as-is. Humans are expected to fill in the Description, Related issue(s), Testing, and Checklist sections.
- **Exception**: If a human **explicitly asks** you to fill out the PR template, then fill it out **in Chinese**, using Chinese characters (简体中文) for all content you add.
## Quick-Reference Workflow
Follow these steps when committing and creating PRs:
1. Make your changes
2. Run `yarn typecheck` — fix any errors
3. Run `yarn lint:fix` — fix any remaining lint errors
4. Run relevant tests (`yarn test` for all, or workspace-specific)
5. Stage files and commit with `[AI]` prefix — do not skip hooks
6. When creating a PR:
- Use `[AI]` prefix in the title (the `"AI generated"` label is auto-applied based on this prefix)
- Leave the PR template blank (do not fill it in)
- **NEVER fill in the PR template** (`.github/PULL_REQUEST_TEMPLATE.md`). Leave all
blank spaces and placeholder comments as-is — the human who tested the change
fills in the Description, Related issue(s), Testing, and Checklist sections.
- **Exception**: if a human **explicitly asks** you to fill it out, do so **in
Chinese**, using Chinese characters (简体中文) for all content you add.
+1 -1
View File
@@ -1 +1 @@
yarn lint-staged
yarn nano-staged
+8
View File
@@ -0,0 +1,8 @@
{
"packages/*/{package.json,tsconfig.json}": [
"ts-node ./bin/validate-publish-imports.ts --fix",
"yarn sync:tsconfig-references"
],
"**/*.{js,mjs,jsx,ts,tsx,md,json,yml,yaml}": "oxfmt --no-error-on-unmatched-pattern",
"**/*.{js,mjs,jsx,ts,tsx}": "oxlint --fix --type-aware --quiet"
}
+11
View File
@@ -359,6 +359,7 @@
]
}
],
"actual/no-enum": "error",
"eslint/no-useless-constructor": "error",
"eslint/no-undef": "error",
"eslint/no-unused-expressions": "error",
@@ -372,6 +373,16 @@
"react/rules-of-hooks": "off"
}
},
{
// Grandfathered pre-existing enums; new enums are disallowed everywhere else.
"files": [
"packages/component-library/src/tokens.ts",
"packages/desktop-client/src/auth/types.ts"
],
"rules": {
"actual/no-enum": "off"
}
},
{
"files": ["**/*.test.{js,ts,jsx,tsx}", "packages/docs/**/*"],
"rules": {
+26 -76
View File
@@ -42,11 +42,10 @@ yarn start:desktop
- Use `yarn workspace <workspace-name> run <command>` for workspace-specific tasks
- Tests run once and exit by default (using `vitest --run`)
### ⚠️ CRITICAL REQUIREMENT: AI-Generated Commit Messages and PR Titles
### ⚠️ PR titles must start with `[AI]`
**ALL commit messages and PR titles MUST be prefixed with `[AI]`.** No exceptions.
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for the full specification, including git safety rules, pre-commit checklist, and PR workflow.
Every pull request title must be prefixed with `[AI]` — this isn't enforced
automatically, so you have to apply it yourself. See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md).
### Task Orchestration with Lage
@@ -200,10 +199,6 @@ When implementing changes:
1. Read relevant files to understand current implementation
2. Make focused, incremental changes
3. Run type checking: `yarn typecheck`
4. Run linting: `yarn lint:fix`
5. Run relevant tests
6. Fix any linter errors that are introduced
### 2. Testing Strategy
@@ -253,21 +248,20 @@ yarn workspace @actual-app/web e2e
TypeScript configuration uses:
- Incremental compilation
- Strict type checking with `typescript-strict-plugin`
- Strict type checking with `typescript-strict-plugin`. New files must be
type-strict — don't add `// @ts-strict-ignore` to a new file (existing files
are grandfathered).
- Platform-specific exports in `loot-core` (node vs browser)
Always run `yarn typecheck` before committing.
### 4. Internationalization (i18n)
- Use `Trans` component instead of `t()` function when possible
- All user-facing strings must be translated
- Generate i18n files: `yarn generate:i18n`
- Custom ESLint rules enforce translation usage
Use the `Trans` component (and translated strings) for user-facing text.
Regenerate i18n files with `yarn generate:i18n`.
### 5. Financial Number Typography
- Wrap standalone financial numbers with `FinancialText` or apply `styles.tnum` directly if wrapping is not possible
Wrap standalone financial numbers with `FinancialText` (or `styles.tnum` where
wrapping isn't possible).
## Code Style & Conventions
@@ -275,17 +269,12 @@ Always run `yarn typecheck` before committing.
**Type Usage:**
- Use TypeScript for all code
- Prefer `type` over `interface`
- Avoid `enum` - use objects or maps
- Avoid `any` or `unknown` unless absolutely necessary
- Look for existing type definitions in the codebase
- Avoid type assertions (`as`, `!`) - prefer `satisfies`
- Use TypeScript for all code; look for existing type definitions before adding new ones
- Prefer `satisfies` over type assertions (`as`, `!`) for narrowing
**Naming:**
- Use descriptive variable names with auxiliary verbs (e.g., `isLoaded`, `hasError`)
- Named exports for components and utilities (avoid default exports except in specific cases)
**Code Structure:**
@@ -298,14 +287,7 @@ Always run `yarn typecheck` before committing.
**React Patterns:**
- The project uses **React Compiler** (`babel-plugin-react-compiler`) in the desktop-client. The compiler auto-memoizes component bodies, so you can omit manual `useCallback`, `useMemo`, and `React.memo` when adding or refactoring code; prefer inline callbacks and values unless a stable identity is required by a non-compiled dependency.
- Don't use `React.FunctionComponent` or `React.FC` - type props directly
- Don't use `React.*` patterns - use named imports instead
- Use `<Link>` instead of `<a>` tags
- Use custom hooks from `src/hooks` (not react-router directly):
- `useNavigate()` from `src/hooks` (not react-router)
- `useDispatch()`, `useSelector()`, `useStore()` from `src/redux` (not react-redux)
- Avoid unstable nested components
- Use `satisfies` for type narrowing
**JSX Style:**
@@ -314,37 +296,14 @@ Always run `yarn typecheck` before committing.
- Use concise syntax for simple statements
- Prefer explicit expressions (`condition && <Component />`)
### Import Organization
Imports are automatically organized by ESLint with the following order:
1. React imports (first)
2. Built-in Node.js modules
3. External packages
4. Actual packages (`loot-core`, `@actual-app/components` - legacy pattern `loot-design` may appear in old code)
5. Parent imports
6. Sibling imports
7. Index imports
Always maintain newlines between import groups.
### Platform-Specific Code
- Don't directly reference platform-specific imports (`.api`, `.electron`)
- Use conditional exports in `loot-core` for platform-specific code
- Platform resolution happens at build time via package.json exports
- Use conditional exports in `loot-core` for platform-specific code; platform
resolution happens at build time via package.json exports. Don't directly
import another platform's modules (`.api`, `.electron`).
### Restricted Patterns
**Never:**
- Import from `uuid` without destructuring: use `import { v4 as uuidv4 } from 'uuid'`
- Import colors directly - use theme instead
- Import `@actual-app/web/*` in `loot-core`
**Git Commands:**
See [PR and Commit Rules](.github/agents/pr-and-commit-rules.md) for complete git safety rules, commit message requirements, and PR workflow.
For commit and PR rules, see
[PR and Commit Rules](.github/agents/pr-and-commit-rules.md).
## File Structure Patterns
@@ -386,7 +345,10 @@ describe('ComponentName', () => {
- `/package.json` - Root workspace configuration, scripts
- `/lage.config.js` - Lage task runner configuration
- `/eslint.config.mjs` - ESLint configuration (flat config format)
- `/.oxlintrc.json` - Lint rules (oxlint); `/.oxfmtrc.json` - formatting (oxfmt)
- `/.nano-staged.json` - pre-commit format/lint config (run via Husky)
- `/.claude/settings.json`, `/.codex/config.toml`, `/.cursor/hooks.json` - agent
hook wiring; shared scripts live in `/scripts/agent-hooks/`
- `/tsconfig.json` - Root TypeScript configuration
- `/.cursorignore`, `/.gitignore` - Ignored files
- `/yarn.lock` - Dependency lockfile (Yarn 4)
@@ -479,13 +441,9 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
### Linter Errors
1. Run `yarn lint:fix` to auto-fix many issues
2. Check ESLint output for specific rule violations
3. Custom rules:
- `actual/no-untranslated-strings` - Add i18n
- `actual/prefer-trans-over-t` - Use Trans component
- `actual/prefer-logger-over-console` - Use logger
- Check `eslint.config.mjs` for complete rules
Run `yarn lint` to check. All rules — including the custom `actual/*` rules
(`no-untranslated-strings`, `prefer-trans-over-t`, `prefer-logger-over-console`,
`typography`, …) — are defined in [`.oxlintrc.json`](.oxlintrc.json).
### Test Failures
@@ -501,7 +459,7 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
1. Check `tsconfig.json` for path mappings
2. Check package.json `exports` field (especially for loot-core)
3. Verify platform-specific imports (`.electron`, `.api`)
4. Use absolute imports in `desktop-client` (enforced by ESLint)
4. Use absolute imports in `desktop-client`
### Build Failures
@@ -544,15 +502,7 @@ Icons in `packages/component-library/src/icons/` are auto-generated. Don't manua
Before committing changes, ensure:
- [ ] Commit and PR rules followed (see [PR and Commit Rules](.github/agents/pr-and-commit-rules.md))
- [ ] `yarn typecheck` passes
- [ ] `yarn lint:fix` has been run
- [ ] Relevant tests pass
- [ ] User-facing strings are translated
- [ ] Prefer `type` over `interface`
- [ ] Named exports used (not default exports)
- [ ] Imports are properly ordered
- [ ] Platform-specific code uses proper exports
- [ ] No unnecessary type assertions
## Pull Request Guidelines
@@ -633,7 +583,7 @@ When running the app for manual testing or demos, use **"View demo"** on the ini
### Gotchas
- The `engines` field requires **Node.js >=22** and **Yarn ^4.9.1**. The `.nvmrc` specifies `v22/*`.
- Pre-commit hook runs `lint-staged` (oxfmt + oxlint) via Husky. Run `yarn prepare` once after install to set up hooks.
- Pre-commit hook runs `nano-staged` (oxfmt + oxlint, configured in `.nano-staged.json`) via Husky. Run `yarn prepare` once after install to set up hooks.
- Lage caches test results in `.lage/`. If tests behave unexpectedly, clear with `rm -rf .lage`.
- Native modules (`better-sqlite3`, `bcrypt`) require build tools (`gcc`, `make`, `python3`). These are pre-installed in the Cloud VM.
- All yarn commands must be run from the repository root, never from child workspaces.
+1 -13
View File
@@ -75,8 +75,8 @@
"eslint-plugin-typescript-paths": "^0.0.33",
"husky": "^9.1.7",
"lage": "^2.15.5",
"lint-staged": "^16.4.0",
"minimatch": "^10.2.5",
"nano-staged": "^0.8.0",
"npm-run-all": "^4.1.5",
"oxfmt": "^0.44.0",
"oxlint": "^1.59.0",
@@ -114,18 +114,6 @@
"socks": ">=2.8.3",
"webpackbar": "^7.0.0"
},
"lint-staged": {
"packages/*/{package.json,tsconfig.json}": [
"ts-node ./bin/validate-publish-imports.ts --fix",
"yarn sync:tsconfig-references"
],
"*.{js,mjs,jsx,ts,tsx,md,json,yml,yaml}": [
"oxfmt --no-error-on-unmatched-pattern"
],
"*.{js,mjs,jsx,ts,tsx}": [
"oxlint --fix --type-aware --quiet"
]
},
"browserslist": [
"electron >= 35.0",
"defaults"
@@ -13,6 +13,7 @@ module.exports = eslintCompatPlugin({
'object-shorthand-properties': require('./rules/object-shorthand-properties'),
'prefer-const': require('./rules/prefer-const'),
'no-anchor-tag': require('./rules/no-anchor-tag'),
'no-enum': require('./rules/no-enum'),
'no-react-default-import': require('./rules/no-react-default-import'),
'prefer-subpath-imports': require('./rules/prefer-subpath-imports'),
'enforce-boundaries': require('./rules/enforce-boundaries'),
@@ -0,0 +1,31 @@
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'Forbid TypeScript enum declarations in favor of objects or maps',
},
fixable: null,
schema: [],
messages: {
noEnum:
'Avoid `enum` — use an object or a map instead (see contributing/code-style.md).',
},
},
createOnce(context) {
return {
TSEnumDeclaration(node) {
context.report({
node,
messageId: 'noEnum',
});
},
};
},
};
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# Agent hook (Stop), shared across Claude and Codex.
#
# When the agent finishes its turn, run typecheck + test for each workspace this
# branch has touched (scoped — far cheaper than the whole-monorepo check).
# Failures are surfaced back to the agent via exit code 2 so it can fix them.
# Adapted from a suggestion by @StephenBrown2 on PR #8089.
set -uo pipefail
# Read the hook payload (Claude provides stop_hook_active to prevent loops).
payload=$(cat 2>/dev/null || true)
if printf '%s' "$payload" | jq -e '.stop_hook_active == true' >/dev/null 2>&1; then
exit 0
fi
# Resolve the repo root regardless of which agent invoked us.
. "$(dirname "$0")/common.sh"
ROOT=$(resolve_repo_root)
cd "$ROOT" 2>/dev/null || exit 0
# Skip if dependencies aren't installed.
[ -f "$ROOT/node_modules/.yarn-state.yml" ] || exit 0
# Collect changed files, NUL-delimited so paths with spaces survive: this
# branch's commits vs master, plus tracked + untracked working-tree changes.
base=$(git merge-base HEAD origin/master 2>/dev/null || true)
files=()
while IFS= read -r -d '' f; do
[ -n "$f" ] && files+=("$f")
done < <(
{
[ -n "$base" ] && git diff -z --name-only "$base"...HEAD
git diff -z --name-only HEAD
git ls-files -z --others --exclude-standard
} 2>/dev/null
)
[ "${#files[@]}" -gt 0 ] || exit 0
# Map changed source files to the workspace dir that owns them (deduped).
# Stock macOS ships bash 3.2, which has no associative arrays (`declare -A`) and
# evaluates `arr[$d]` subscripts as arithmetic (slashes/hyphens in a path then
# blow up under `set -u`). So dedupe via a space-padded string match instead —
# `ws_dirs` stays a plain indexed array, which bash 3.2 handles fine.
seen=" "
ws_dirs=()
for f in "${files[@]}"; do
case "$f" in
*.js | *.mjs | *.jsx | *.ts | *.tsx) ;;
*) continue ;;
esac
d=$(dirname "$f")
while [ "$d" != "." ] && [ "$d" != "/" ]; do
if [ -f "$ROOT/$d/package.json" ]; then
case "$seen" in
*" $d "*) ;;
*)
seen="$seen$d "
ws_dirs+=("$d")
;;
esac
break
fi
d=$(dirname "$d")
done
done
[ "${#ws_dirs[@]}" -gt 0 ] || exit 0
fail=0
report=""
for d in "${ws_dirs[@]}"; do
# One jq read per package.json: name + which scripts exist. Every field is a
# non-empty token (the name falls back to "-", the flags to yes/no) so that
# `read` can't collapse an empty middle field — tab is IFS-whitespace, so a
# package with test-but-no-typecheck would otherwise shift the columns.
IFS=$'\t' read -r name tc te < <(
jq -r '[.name // "-",
(if .scripts.typecheck then "yes" else "no" end),
(if .scripts.test then "yes" else "no" end)] | @tsv' "$ROOT/$d/package.json"
)
[ "$name" != "-" ] || continue
if [ "$tc" = "yes" ]; then
if ! out=$(yarn workspace "$name" run typecheck 2>&1); then
report="$report"$'\n'"[typecheck failed: $name]"$'\n'"$(printf '%s' "$out" | tail -25)"
fail=1
fi
fi
if [ "$te" = "yes" ]; then
if ! out=$(yarn workspace "$name" run test 2>&1); then
report="$report"$'\n'"[tests failed: $name]"$'\n'"$(printf '%s' "$out" | tail -25)"
fail=1
fi
fi
done
if [ "$fail" -eq 1 ]; then
printf 'Stop check found failures in touched workspaces:%s\n' "$report" >&2
exit 2
fi
exit 0
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
# Shared helpers for the agent-hook scripts. Source this file; don't execute it.
# Resolve the repo root regardless of which agent invoked the hook.
# Optional $1: a directory hint for the `git` fallback (e.g. an edited file's dir).
resolve_repo_root() {
if [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
printf '%s\n' "$CLAUDE_PROJECT_DIR"
return 0
fi
_hint=${1:-.}
_root=$(git -C "$_hint" rev-parse --show-toplevel 2>/dev/null) || _root=
[ -n "$_root" ] || _root=$(CDPATH= cd "$(dirname "$0")/../.." && pwd)
printf '%s\n' "$_root"
}
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh
# Agent hook (PostToolUse / afterFileEdit), shared across Claude, Codex, Cursor.
#
# Formats and lints just the single file the agent edited, so generated code
# stays consistent with oxfmt/oxlint without relying on the agent remembering to
# run `yarn lint:fix`. See AGENTS.md "Agent hooks".
#
# `--type-aware` is intentionally skipped here to keep the edit loop fast; the
# type-aware pass still runs at commit time via nano-staged.
file=$(jq -r '.tool_input.file_path // empty')
[ -n "$file" ] && [ -f "$file" ] || exit 0
# Resolve the repo root regardless of which agent invoked us.
. "$(dirname "$0")/common.sh"
ROOT=$(resolve_repo_root "$(dirname "$file")")
BIN="$ROOT/node_modules/.bin"
[ -x "$BIN/oxfmt" ] || exit 0
case "$file" in
*.js | *.mjs | *.jsx | *.ts | *.tsx | *.md | *.json | *.yml | *.yaml)
"$BIN/oxfmt" --no-error-on-unmatched-pattern "$file" >/dev/null 2>&1
;;
esac
case "$file" in
*.js | *.mjs | *.jsx | *.ts | *.tsx)
"$BIN/oxlint" --fix --quiet "$file" >/dev/null 2>&1
;;
esac
exit 0
+101
View File
@@ -0,0 +1,101 @@
#!/bin/sh
# Shared agent guard for shell/Bash commands (Claude PreToolUse[Bash],
# Codex PreToolUse[^Bash$], Cursor beforeShellExecution via adapter).
#
# Deterministically enforces the project's git-safety and workspace rules that
# used to live as prose in AGENTS.md / .github/agents/pr-and-commit-rules.md.
# Reads the command from `.tool_input.command` on stdin. Exit code 2 + stderr
# blocks the call and feeds the reason back to the agent.
#
# Best-effort by design: it catches the honest mistakes agents actually make
# (plain push-to-main, a forgotten [AI] commit prefix, --no-verify, yarn in a
# workspace). It does NOT try to defend against deliberate evasion via unusual
# shell forms (git global options before the subcommand, `git commit -F`/`-C`/
# `--amend`, etc.) — CI and branch protection are the real gates for what lands.
block() {
echo "$1" >&2
exit 2
}
# Fail closed: a malformed payload (jq parse failure) blocks rather than allows.
cmd=$(jq -r '.tool_input.command // empty' 2>/dev/null) ||
block "Blocked: could not parse the hook payload (.tool_input.command)."
[ -z "$cmd" ] && exit 0
# Enforce: always run yarn from the repo root, never in a child workspace.
# Catch cd/pushd into a package dir, or `yarn --cwd packages/...`.
case "$cmd" in
*"cd packages/"* | *"cd ./packages/"* | *"cd packages "* | *"cd ./packages "* | \
*"pushd packages/"* | *"pushd ./packages/"* | *"pushd packages "* | *"pushd ./packages "* | \
*"yarn --cwd packages/"* | *"yarn --cwd ./packages/"* | *"yarn --cwd packages "* | *"yarn --cwd ./packages "* | \
*"yarn --cwd=packages/"* | *"yarn --cwd=./packages/"* | *"yarn --cwd=packages "* | *"yarn --cwd=./packages "*)
case "$cmd" in
*yarn*)
block "Blocked: run yarn from the repo root, not a child workspace (AGENTS.md). Use 'yarn workspace <name> <cmd>' from the root instead." ;;
esac ;;
esac
# Everything below only applies to actual git invocations. Match `git` as a
# whole token (not a substring) so commands that merely mention git — e.g.
# `rg "git config" AGENTS.md` — aren't falsely blocked.
is_git=0
set -f
for tok in $cmd; do
[ "$tok" = "git" ] && {
is_git=1
break
}
done
set +f
[ "$is_git" -eq 1 ] || exit 0
# Never skip git hooks.
case "$cmd" in
*--no-verify* | *--no-gpg-sign*)
block "Blocked: skipping git hooks (--no-verify/--no-gpg-sign) is forbidden." ;;
esac
# Never change git config (reads are fine).
case "$cmd" in
*"git config"*)
case "$cmd" in
*--get* | *--list* | *" -l "* | *" -l") ;;
*) block "Blocked: 'git config' changes are forbidden." ;;
esac ;;
esac
# Never force-push or push to main/master.
case "$cmd" in
*"git push"*)
case "$cmd" in
*--force* | *" -f "* | *" -f")
block "Blocked: force push only on explicit user request. Use --force-with-lease and confirm with the user first." ;;
esac
# Match main/master only as a whole ref/token (padding avoids matching
# branches like "maintenance" or "main-feature"). Also catch explicit
# refspecs like `HEAD:refs/heads/main` / `origin refs/heads/master`.
case " $cmd " in
*" main "* | *" master "* | *":main "* | *":master "* | \
*" refs/heads/main "* | *" refs/heads/master "* | \
*":refs/heads/main "* | *":refs/heads/master "*)
block "Blocked: never push to main/master. Push the feature branch instead." ;;
esac ;;
esac
# Commit messages must start with [AI].
case "$cmd" in
*"git commit"*)
# Extract the message from -m / --message (quoted first, then unquoted).
# An empty result means no inline message (editor/--amend) — left alone.
msg=$(printf '%s' "$cmd" | sed -n "s/.*-m[[:space:]]*['\"]\\([^'\"]*\\).*/\\1/p")
[ -n "$msg" ] || msg=$(printf '%s' "$cmd" | sed -n "s/.*--message[=[:space:]][[:space:]]*['\"]\\([^'\"]*\\).*/\\1/p")
[ -n "$msg" ] || msg=$(printf '%s' "$cmd" | sed -n "s/.*-m[[:space:]][[:space:]]*\\([^[:space:]'\"][^[:space:]]*\\).*/\\1/p")
[ -n "$msg" ] || msg=$(printf '%s' "$cmd" | sed -n "s/.*--message[=[:space:]][[:space:]]*\\([^[:space:]'\"][^[:space:]]*\\).*/\\1/p")
case "$msg" in
"" | "[AI]"*) ;;
*) block "Blocked: commit messages must start with '[AI]'. Got: $msg" ;;
esac ;;
esac
exit 0
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
# Shared agent guard for new-file creation (Claude PreToolUse[Write],
# Codex PreToolUse[file-edit]). Cursor can only run this advisorily (its
# afterFileEdit cannot block) — see .cursor/hooks/after-file-edit.sh.
#
# New TypeScript files must be type-strict: block creating a brand-new file that
# contains `// @ts-strict-ignore`. Existing files (241 of them) are grandfathered
# automatically because this only fires when the target path does not yet exist.
input=$(cat)
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[ -n "$file" ] || exit 0
case "$file" in
*.ts | *.tsx) ;;
*) exit 0 ;;
esac
# Only guard new files; edits to existing files are untouched.
[ -f "$file" ] && exit 0
content=$(printf '%s' "$input" | jq -r '.tool_input.content // empty')
case "$content" in
*"@ts-strict-ignore"*)
echo "Blocked: new files must be type-strict — remove '// @ts-strict-ignore' and fix the types instead (AGENTS.md)." >&2
exit 2
;;
esac
exit 0
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# Agent hook (PostToolUse / afterFileEdit), shared across Claude, Codex, Cursor.
#
# "Prefer one component per file" — surfaced as a soft warning for *newly
# created* .tsx files only. Existing multi-component files are grandfathered
# (skipped if already tracked by git), and this never touches `yarn lint`, so
# react/no-multi-comp stays out of .oxlintrc.json.
file=$(jq -r '.tool_input.file_path // empty')
[ -n "$file" ] && [ -f "$file" ] || exit 0
case "$file" in
*.tsx) ;;
*) exit 0 ;;
esac
dir=$(dirname "$file")
# Resolve the repo root regardless of which agent invoked us.
. "$(dirname "$0")/common.sh"
ROOT=$(resolve_repo_root "$dir")
# Normalize to an absolute + repo-root-relative path so the git pathspec is
# unambiguous whether the agent passed an absolute or relative file_path.
abs=$(CDPATH= cd "$dir" 2>/dev/null && pwd)/$(basename "$file")
rel=${abs#"$ROOT"/}
# Only new (untracked) files; grandfather everything already in git.
git -C "$ROOT" ls-files --error-unmatch "$rel" >/dev/null 2>&1 && exit 0
BIN="$ROOT/node_modules/.bin"
[ -x "$BIN/oxlint" ] || exit 0
out=$(cd "$ROOT" && "$BIN/oxlint" -D react/no-multi-comp "$abs" 2>&1)
case "$out" in
*no-multi-comp*)
echo "Prefer one component per file — this new file defines multiple components; split them into separate files (AGENTS.md)." >&2
exit 2
;;
esac
exit 0
+6
View File
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
Move mechanical agent rules into shared agent hooks (wired for Claude, Codex, and Cursor).
+16 -161
View File
@@ -10969,8 +10969,8 @@ __metadata:
glob-hasher-linux-arm64-gnu: "npm:1.4.2"
husky: "npm:^9.1.7"
lage: "npm:^2.15.5"
lint-staged: "npm:^16.4.0"
minimatch: "npm:^10.2.5"
nano-staged: "npm:^0.8.0"
npm-run-all: "npm:^4.1.5"
oxfmt: "npm:^0.44.0"
oxlint: "npm:^1.59.0"
@@ -11153,15 +11153,6 @@ __metadata:
languageName: node
linkType: hard
"ansi-escapes@npm:^7.0.0":
version: 7.1.1
resolution: "ansi-escapes@npm:7.1.1"
dependencies:
environment: "npm:^1.0.0"
checksum: 10/a67a9fd832e5e4e9c854bb1aab233416b1fcac373377fd6cc703926eb7da990b5fd12f5e37fab00f3d88dc1ebc8bd1ecffb76750e92c8cbdaa3d82038d3907d8
languageName: node
linkType: hard
"ansi-html-community@npm:^0.0.8":
version: 0.0.8
resolution: "ansi-html-community@npm:0.0.8"
@@ -12764,15 +12755,6 @@ __metadata:
languageName: node
linkType: hard
"cli-cursor@npm:^5.0.0":
version: 5.0.0
resolution: "cli-cursor@npm:5.0.0"
dependencies:
restore-cursor: "npm:^5.0.0"
checksum: 10/1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090
languageName: node
linkType: hard
"cli-spinners@npm:^2.5.0":
version: 2.9.2
resolution: "cli-spinners@npm:2.9.2"
@@ -12803,16 +12785,6 @@ __metadata:
languageName: node
linkType: hard
"cli-truncate@npm:^5.0.0":
version: 5.1.0
resolution: "cli-truncate@npm:5.1.0"
dependencies:
slice-ansi: "npm:^7.1.0"
string-width: "npm:^8.0.0"
checksum: 10/3a45844202d456548b371f6b99c5af50c43dc8cc7384378d754e5f36c302eb589c50dc6dbbd815c42411d37895d19ae88228062767e37439de55acd0de137f50
languageName: node
linkType: hard
"client-only@npm:^0.0.1":
version: 0.0.1
resolution: "client-only@npm:0.0.1"
@@ -13025,7 +12997,7 @@ __metadata:
languageName: node
linkType: hard
"colorette@npm:^2.0.10, colorette@npm:^2.0.20":
"colorette@npm:^2.0.10":
version: 2.0.20
resolution: "colorette@npm:2.0.20"
checksum: 10/0b8de48bfa5d10afc160b8eaa2b9938f34a892530b2f7d7897e0458d9535a066e3998b49da9d21161c78225b272df19ae3a64d6df28b4c9734c0e55bbd02406f
@@ -15272,13 +15244,6 @@ __metadata:
languageName: node
linkType: hard
"environment@npm:^1.0.0":
version: 1.1.0
resolution: "environment@npm:1.1.0"
checksum: 10/dd3c1b9825e7f71f1e72b03c2344799ac73f2e9ef81b78ea8b373e55db021786c6b9f3858ea43a436a2c4611052670ec0afe85bc029c384cc71165feee2f4ba6
languageName: node
linkType: hard
"eol@npm:^0.9.1":
version: 0.9.1
resolution: "eol@npm:0.9.1"
@@ -16864,7 +16829,7 @@ __metadata:
languageName: node
linkType: hard
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.0, get-east-asian-width@npm:^1.3.1":
"get-east-asian-width@npm:^1.0.0":
version: 1.4.0
resolution: "get-east-asian-width@npm:1.4.0"
checksum: 10/c9ae85bfc2feaf4cc71cdb236e60f1757ae82281964c206c6aa89a25f1987d326ddd8b0de9f9ccd56e37711b9fcd988f7f5137118b49b0b45e19df93c3be8f45
@@ -18535,15 +18500,6 @@ __metadata:
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^5.0.0":
version: 5.1.0
resolution: "is-fullwidth-code-point@npm:5.1.0"
dependencies:
get-east-asian-width: "npm:^1.3.1"
checksum: 10/4700d8a82cb71bd2a2955587b2823c36dc4660eadd4047bfbd070821ddbce8504fc5f9b28725567ecddf405b1e06c6692c9b719f65df6af9ec5262bc11393a6a
languageName: node
linkType: hard
"is-generator-function@npm:^1.0.10, is-generator-function@npm:^1.0.7":
version: 1.1.2
resolution: "is-generator-function@npm:1.1.2"
@@ -19590,22 +19546,6 @@ __metadata:
languageName: node
linkType: hard
"lint-staged@npm:^16.4.0":
version: 16.4.0
resolution: "lint-staged@npm:16.4.0"
dependencies:
commander: "npm:^14.0.3"
listr2: "npm:^9.0.5"
picomatch: "npm:^4.0.3"
string-argv: "npm:^0.3.2"
tinyexec: "npm:^1.0.4"
yaml: "npm:^2.8.2"
bin:
lint-staged: bin/lint-staged.js
checksum: 10/eb7aa0d43e321bbf282ce0c693a3db762aa9b8e5bf29419a49c8877a247cd3a14cf8d519f914f8c8dcd2aea73ac83c0bb44fadb97a30004219e060a780dda74e
languageName: node
linkType: hard
"listify@npm:^1.0.3":
version: 1.0.3
resolution: "listify@npm:1.0.3"
@@ -19613,20 +19553,6 @@ __metadata:
languageName: node
linkType: hard
"listr2@npm:^9.0.5":
version: 9.0.5
resolution: "listr2@npm:9.0.5"
dependencies:
cli-truncate: "npm:^5.0.0"
colorette: "npm:^2.0.20"
eventemitter3: "npm:^5.0.1"
log-update: "npm:^6.1.0"
rfdc: "npm:^1.4.1"
wrap-ansi: "npm:^9.0.0"
checksum: 10/b78ffd60443aed9a8e0fc9162eb941ea4d63210700d61a895eb29348f23fc668327e26bbca87a9e6a6208e7fa96c475fe1f1c6c19b46f376f547e0cba3b935ae
languageName: node
linkType: hard
"load-json-file@npm:^4.0.0":
version: 4.0.0
resolution: "load-json-file@npm:4.0.0"
@@ -19804,19 +19730,6 @@ __metadata:
languageName: node
linkType: hard
"log-update@npm:^6.1.0":
version: 6.1.0
resolution: "log-update@npm:6.1.0"
dependencies:
ansi-escapes: "npm:^7.0.0"
cli-cursor: "npm:^5.0.0"
slice-ansi: "npm:^7.1.0"
strip-ansi: "npm:^7.1.0"
wrap-ansi: "npm:^9.0.0"
checksum: 10/5abb4131e33b1e7f8416bb194fe17a3603d83e4657c5bf5bb81ce4187f3b00ea481643b85c3d5cefe6037a452cdcf7f1391ab8ea0d9c23e75d19589830ec4f11
languageName: node
linkType: hard
"logform@npm:^2.7.0":
version: 2.7.0
resolution: "logform@npm:2.7.0"
@@ -21085,13 +20998,6 @@ __metadata:
languageName: node
linkType: hard
"mimic-function@npm:^5.0.0":
version: 5.0.1
resolution: "mimic-function@npm:5.0.1"
checksum: 10/eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c
languageName: node
linkType: hard
"mimic-response@npm:^1.0.0":
version: 1.0.1
resolution: "mimic-response@npm:1.0.1"
@@ -21385,6 +21291,17 @@ __metadata:
languageName: node
linkType: hard
"nano-staged@npm:^0.8.0":
version: 0.8.0
resolution: "nano-staged@npm:0.8.0"
dependencies:
picocolors: "npm:^1.0.0"
bin:
nano-staged: lib/bin.js
checksum: 10/33051331ee280e42fcb4863662678d9eabb54869282dda4d1425880d1f179a9a720369f9e9b66d551cda334cb41c447209d5884a9172783b0393069f96ae549e
languageName: node
linkType: hard
"nanoid@npm:^3.3.11":
version: 3.3.11
resolution: "nanoid@npm:3.3.11"
@@ -21879,15 +21796,6 @@ __metadata:
languageName: node
linkType: hard
"onetime@npm:^7.0.0":
version: 7.0.0
resolution: "onetime@npm:7.0.0"
dependencies:
mimic-function: "npm:^5.0.0"
checksum: 10/eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c
languageName: node
linkType: hard
"open@npm:^10.0.3, open@npm:^10.2.0":
version: 10.2.0
resolution: "open@npm:10.2.0"
@@ -25330,16 +25238,6 @@ __metadata:
languageName: node
linkType: hard
"restore-cursor@npm:^5.0.0":
version: 5.1.0
resolution: "restore-cursor@npm:5.1.0"
dependencies:
onetime: "npm:^7.0.0"
signal-exit: "npm:^4.1.0"
checksum: 10/838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c
languageName: node
linkType: hard
"retry@npm:^0.12.0":
version: 0.12.0
resolution: "retry@npm:0.12.0"
@@ -25361,13 +25259,6 @@ __metadata:
languageName: node
linkType: hard
"rfdc@npm:^1.4.1":
version: 1.4.1
resolution: "rfdc@npm:1.4.1"
checksum: 10/2f3d11d3d8929b4bfeefc9acb03aae90f971401de0add5ae6c5e38fec14f0405e6a4aad8fdb76344bfdd20c5193110e3750cbbd28ba86d73729d222b6cf4a729
languageName: node
linkType: hard
"rimraf@npm:^2.5.4":
version: 2.7.1
resolution: "rimraf@npm:2.7.1"
@@ -26164,7 +26055,7 @@ __metadata:
languageName: node
linkType: hard
"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0":
"signal-exit@npm:^4.0.1":
version: 4.1.0
resolution: "signal-exit@npm:4.1.0"
checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f
@@ -26273,16 +26164,6 @@ __metadata:
languageName: node
linkType: hard
"slice-ansi@npm:^7.1.0":
version: 7.1.2
resolution: "slice-ansi@npm:7.1.2"
dependencies:
ansi-styles: "npm:^6.2.1"
is-fullwidth-code-point: "npm:^5.0.0"
checksum: 10/75f61e1285c294b18c88521a0cdb22cdcbe9b0fd5e8e26f649be804cc43122aa7751bd960a968e3ed7f5aa7f3c67ac605c939019eae916870ec288e878b6fafb
languageName: node
linkType: hard
"slug@npm:^8.2.2":
version: 8.2.3
resolution: "slug@npm:8.2.3"
@@ -26652,13 +26533,6 @@ __metadata:
languageName: node
linkType: hard
"string-argv@npm:^0.3.2":
version: 0.3.2
resolution: "string-argv@npm:0.3.2"
checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af
languageName: node
linkType: hard
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
version: 4.2.3
resolution: "string-width@npm:4.2.3"
@@ -26692,16 +26566,6 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^8.0.0":
version: 8.1.0
resolution: "string-width@npm:8.1.0"
dependencies:
get-east-asian-width: "npm:^1.3.0"
strip-ansi: "npm:^7.1.0"
checksum: 10/51ee97c4ffee7b94f8a2ee785fac14f81ec9809b9fcec9a4db44e25c717c263af0cc4387c111aef76195c0718dc43766f3678c07fb542294fb0244f7bfbde883
languageName: node
linkType: hard
"string.prototype.matchall@npm:^4.0.6":
version: 4.0.12
resolution: "string.prototype.matchall@npm:4.0.12"
@@ -27343,7 +27207,7 @@ __metadata:
languageName: node
linkType: hard
"tinyexec@npm:^1.0.1, tinyexec@npm:^1.0.2, tinyexec@npm:^1.0.4":
"tinyexec@npm:^1.0.1, tinyexec@npm:^1.0.2":
version: 1.0.4
resolution: "tinyexec@npm:1.0.4"
checksum: 10/ccebe4044eef6fa5050929df7862fda70b4fb700f15d94aef8ae6109b9d194dbc3a990125d99944fd25b90fe2115e1927f055b909a604c571a81b647ede5757a
@@ -29592,15 +29456,6 @@ __metadata:
languageName: node
linkType: hard
"yaml@npm:^2.8.2":
version: 2.8.2
resolution: "yaml@npm:2.8.2"
bin:
yaml: bin.mjs
checksum: 10/4eab0074da6bc5a5bffd25b9b359cf7061b771b95d1b3b571852098380db3b1b8f96e0f1f354b56cc7216aa97cea25163377ccbc33a2e9ce00316fe8d02f4539
languageName: node
linkType: hard
"yargs-parser@npm:^18.1.3":
version: 18.1.3
resolution: "yargs-parser@npm:18.1.3"