@financebuddha/standards 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.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/bin/init.mjs +126 -0
- package/commitlint/config.ts +21 -0
- package/commitlint/index.js +32 -0
- package/eslint/nextjs.mjs +24 -0
- package/husky/commit-msg +7 -0
- package/husky/pre-commit +36 -0
- package/package.json +66 -0
- package/prettier/config.json +8 -0
- package/secrets-patterns.txt +32 -0
- package/skills/pre-commit-checks.md +167 -0
- package/typescript/base.json +19 -0
- package/typescript/nextjs.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FinanceBuddha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @financebuddha/standards
|
|
2
|
+
|
|
3
|
+
Shared engineering standards for all FinanceBuddha repositories — one installed
|
|
4
|
+
package that every repo pulls its ESLint, TypeScript, Prettier, commitlint,
|
|
5
|
+
secrets-scanning, and Claude-skill config from. Upgrade the package, upgrade
|
|
6
|
+
every rule.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Published to the public npm registry under the `@financebuddha` scope:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# bun
|
|
14
|
+
bun add -D @financebuddha/standards
|
|
15
|
+
|
|
16
|
+
# npm
|
|
17
|
+
npm install --save-dev @financebuddha/standards
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
> Installing from GitHub directly (e.g. to test an unpublished commit) also
|
|
21
|
+
> works: `npm i -D github:financebuddha/engineering-standards#<ref>`.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
After installing, scaffold the thin re-export config files into your project:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @financebuddha/standards init # writes config files (skips existing)
|
|
29
|
+
npx @financebuddha/standards init --force # overwrite existing
|
|
30
|
+
npx @financebuddha/standards init --skill # also install the Claude skill
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`init` writes files that **re-export** from the installed package, so nothing is
|
|
34
|
+
duplicated. It prints the two manual one-liners (tsconfig + Prettier) at the end.
|
|
35
|
+
|
|
36
|
+
## What's inside
|
|
37
|
+
|
|
38
|
+
| Subpath / file | How you use it |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `@financebuddha/standards/eslint/nextjs` | `export { default } from '@financebuddha/standards/eslint/nextjs'` in `eslint.config.mjs` |
|
|
41
|
+
| `@financebuddha/standards/typescript/nextjs` | `{ "extends": "@financebuddha/standards/typescript/nextjs" }` in `tsconfig.json` |
|
|
42
|
+
| `@financebuddha/standards/typescript/base` | tsconfig base for non-Next.js packages |
|
|
43
|
+
| `@financebuddha/standards/prettier` | `"prettier": "@financebuddha/standards/prettier"` in `package.json` |
|
|
44
|
+
| `@financebuddha/standards/commitlint` | `export { default } from '@financebuddha/standards/commitlint'` in `commitlint.config.js` |
|
|
45
|
+
| `secrets-patterns.txt` | grep patterns for the secrets scan (CI + husky hook) |
|
|
46
|
+
| `husky/*` | pre-commit (secrets scan + lint-staged) and commit-msg (commitlint) hooks |
|
|
47
|
+
| `skills/pre-commit-checks.md` | Claude skill: secrets → lint → typecheck → tests |
|
|
48
|
+
|
|
49
|
+
## Manual reference (what `init` automates)
|
|
50
|
+
|
|
51
|
+
### ESLint — `eslint.config.mjs`
|
|
52
|
+
```js
|
|
53
|
+
export { default } from '@financebuddha/standards/eslint/nextjs'
|
|
54
|
+
|
|
55
|
+
// To add project rules, spread the preset instead:
|
|
56
|
+
// import base from '@financebuddha/standards/eslint/nextjs'
|
|
57
|
+
// export default [...base, { rules: { 'no-console': 'warn' } }]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### TypeScript — `tsconfig.json`
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"extends": "@financebuddha/standards/typescript/nextjs",
|
|
64
|
+
"compilerOptions": { "paths": { "@/*": ["./*"] } }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Prettier — `package.json`
|
|
69
|
+
```json
|
|
70
|
+
{ "prettier": "@financebuddha/standards/prettier" }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### commitlint — `commitlint.config.js`
|
|
74
|
+
```js
|
|
75
|
+
export { default } from '@financebuddha/standards/commitlint'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Husky — `package.json`
|
|
79
|
+
```json
|
|
80
|
+
{ "scripts": { "prepare": "husky" } }
|
|
81
|
+
```
|
|
82
|
+
Then `npx @financebuddha/standards init` drops the hooks into `.husky/`.
|
|
83
|
+
|
|
84
|
+
### Claude skill
|
|
85
|
+
```bash
|
|
86
|
+
npx @financebuddha/standards init --skill
|
|
87
|
+
# copies skills/pre-commit-checks.md → ~/.claude/skills/
|
|
88
|
+
# invoke with /pre-commit-checks in any Claude Code session
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Secrets scanning
|
|
92
|
+
|
|
93
|
+
`secrets-patterns.txt` is the single source of truth — referenced by both the
|
|
94
|
+
husky `pre-commit` hook and CI (`.github/workflows/ci.yml`). Add a pattern once
|
|
95
|
+
and both layers pick it up. Covers AWS keys, OpenAI/GitHub/Slack tokens, private
|
|
96
|
+
key blocks, JWTs, Google API keys, and generic `secret = "…"` assignments.
|
|
97
|
+
|
|
98
|
+
## Releasing a new version
|
|
99
|
+
|
|
100
|
+
Releases are automated with [release-please](https://github.com/googleapis/release-please)
|
|
101
|
+
driven by conventional commits — you never bump the version by hand.
|
|
102
|
+
|
|
103
|
+
1. Merge conventional commits to `main` (`feat:`, `fix:`, etc.).
|
|
104
|
+
2. release-please opens/updates a **"chore(main): release x.y.z"** PR that bumps
|
|
105
|
+
`package.json` + `.release-please-manifest.json` and writes `CHANGELOG.md`.
|
|
106
|
+
- `feat:` → minor (patch while < 1.0), `fix:` → patch, `feat!:`/`BREAKING CHANGE` → major.
|
|
107
|
+
3. Merge that PR. release-please creates the git tag + GitHub Release, which
|
|
108
|
+
triggers the **publish** job → `npm publish --provenance --access public`.
|
|
109
|
+
|
|
110
|
+
Consuming repos pick up new rules with `npm update @financebuddha/standards`
|
|
111
|
+
(or bump the version range in `package.json`).
|
|
112
|
+
|
|
113
|
+
### One-time setup for publishing
|
|
114
|
+
|
|
115
|
+
1. **npm org** — an npm org named **`financebuddha`** must exist (owns the
|
|
116
|
+
`@financebuddha` scope). Create it at npmjs.com → Add Organization.
|
|
117
|
+
2. **`NPM_TOKEN` secret** — an npm **Automation** access token with publish
|
|
118
|
+
rights to the scope, added under Settings → Secrets and variables → Actions.
|
|
119
|
+
3. **release-please token** — this org disables write access for the default
|
|
120
|
+
Actions token, so add a **`RELEASE_PLEASE_TOKEN`** secret: a fine-grained PAT
|
|
121
|
+
(or classic PAT with `repo` + `workflow` scope) that can open PRs and push
|
|
122
|
+
tags on this repo. _(If an org admin instead enables Settings → Actions →
|
|
123
|
+
"Workflow permissions: read and write" + "Allow Actions to create and approve
|
|
124
|
+
pull requests", the workflow falls back to the built-in token and this secret
|
|
125
|
+
becomes unnecessary.)_
|
|
126
|
+
|
|
127
|
+
## Commit convention
|
|
128
|
+
|
|
129
|
+
All commits follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
130
|
+
See `commitlint/config.ts` for the annotated ruleset.
|
package/bin/init.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @financebuddha/standards — project initializer
|
|
4
|
+
*
|
|
5
|
+
* npx @financebuddha/standards init [options]
|
|
6
|
+
*
|
|
7
|
+
* Scaffolds the thin "re-export" config files into the current project so that
|
|
8
|
+
* every shared rule is pulled live from this installed package (node_modules) —
|
|
9
|
+
* nothing is duplicated, so an upgrade of the package upgrades every rule.
|
|
10
|
+
*
|
|
11
|
+
* Files it can write (each skipped if it already exists, unless --force):
|
|
12
|
+
* eslint.config.mjs → re-exports @financebuddha/standards/eslint/nextjs
|
|
13
|
+
* .prettierrc → { "...": "@financebuddha/standards/prettier" } note below
|
|
14
|
+
* commitlint.config.js → re-exports @financebuddha/standards/commitlint
|
|
15
|
+
* .husky/pre-commit → secrets scan + lint-staged
|
|
16
|
+
* .husky/commit-msg → commitlint
|
|
17
|
+
* secrets-patterns.txt → copied to project root (husky hook reads it)
|
|
18
|
+
*
|
|
19
|
+
* Flags:
|
|
20
|
+
* --force overwrite files that already exist
|
|
21
|
+
* --skill also copy the pre-commit-checks Claude skill to ~/.claude/skills/
|
|
22
|
+
* --no-husky skip writing husky hooks
|
|
23
|
+
* --help show this help
|
|
24
|
+
*
|
|
25
|
+
* Note on Prettier: Prettier cannot "extend" a config the way ESLint/TS can, so
|
|
26
|
+
* the recommended approach is to set "prettier": "@financebuddha/standards/prettier"
|
|
27
|
+
* in your package.json. This script prints that snippet rather than guessing how
|
|
28
|
+
* to edit your package.json.
|
|
29
|
+
*/
|
|
30
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, chmodSync } from 'node:fs'
|
|
31
|
+
import { dirname, join, resolve } from 'node:path'
|
|
32
|
+
import { fileURLToPath } from 'node:url'
|
|
33
|
+
import { homedir } from 'node:os'
|
|
34
|
+
|
|
35
|
+
const PKG = '@financebuddha/standards'
|
|
36
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
37
|
+
const PKG_ROOT = resolve(__dirname, '..')
|
|
38
|
+
const CWD = process.cwd()
|
|
39
|
+
|
|
40
|
+
const args = new Set(process.argv.slice(2))
|
|
41
|
+
const FORCE = args.has('--force')
|
|
42
|
+
const WANT_SKILL = args.has('--skill')
|
|
43
|
+
const NO_HUSKY = args.has('--no-husky')
|
|
44
|
+
|
|
45
|
+
if (args.has('--help') || args.has('-h')) {
|
|
46
|
+
console.log(readFileSync(fileURLToPath(import.meta.url), 'utf8').split('*/')[0].replace(/^#![^\n]*\n/, '').replace(/\/\*\*?|^ \* ?/gm, ''))
|
|
47
|
+
process.exit(0)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const written = []
|
|
51
|
+
const skipped = []
|
|
52
|
+
|
|
53
|
+
/** Write a file unless it exists (or --force). Track outcome for the summary. */
|
|
54
|
+
function place(relPath, contents, { exec = false } = {}) {
|
|
55
|
+
const dest = join(CWD, relPath)
|
|
56
|
+
if (existsSync(dest) && !FORCE) {
|
|
57
|
+
skipped.push(relPath)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
mkdirSync(dirname(dest), { recursive: true })
|
|
61
|
+
writeFileSync(dest, contents)
|
|
62
|
+
if (exec) chmodSync(dest, 0o755)
|
|
63
|
+
written.push(relPath)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── eslint.config.mjs ─────────────────────────────────────────────────────────
|
|
67
|
+
place(
|
|
68
|
+
'eslint.config.mjs',
|
|
69
|
+
`// Pulled from ${PKG}. Add project-specific rules by spreading the preset.\n` +
|
|
70
|
+
`export { default } from '${PKG}/eslint/nextjs'\n`
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// ── commitlint.config.js ──────────────────────────────────────────────────────
|
|
74
|
+
place(
|
|
75
|
+
'commitlint.config.js',
|
|
76
|
+
`// Pulled from ${PKG}.\n` + `export { default } from '${PKG}/commitlint'\n`
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// ── secrets-patterns.txt (copied — husky hook greps it locally) ───────────────
|
|
80
|
+
{
|
|
81
|
+
const dest = join(CWD, 'secrets-patterns.txt')
|
|
82
|
+
if (existsSync(dest) && !FORCE) {
|
|
83
|
+
skipped.push('secrets-patterns.txt')
|
|
84
|
+
} else {
|
|
85
|
+
copyFileSync(join(PKG_ROOT, 'secrets-patterns.txt'), dest)
|
|
86
|
+
written.push('secrets-patterns.txt')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── husky hooks ───────────────────────────────────────────────────────────────
|
|
91
|
+
if (!NO_HUSKY) {
|
|
92
|
+
place('.husky/pre-commit', readFileSync(join(PKG_ROOT, 'husky/pre-commit'), 'utf8'), { exec: true })
|
|
93
|
+
place('.husky/commit-msg', readFileSync(join(PKG_ROOT, 'husky/commit-msg'), 'utf8'), { exec: true })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Claude skill (opt-in) ─────────────────────────────────────────────────────
|
|
97
|
+
if (WANT_SKILL) {
|
|
98
|
+
const skillsDir = join(homedir(), '.claude', 'skills')
|
|
99
|
+
const dest = join(skillsDir, 'pre-commit-checks.md')
|
|
100
|
+
if (existsSync(dest) && !FORCE) {
|
|
101
|
+
skipped.push('~/.claude/skills/pre-commit-checks.md')
|
|
102
|
+
} else {
|
|
103
|
+
mkdirSync(skillsDir, { recursive: true })
|
|
104
|
+
copyFileSync(join(PKG_ROOT, 'skills/pre-commit-checks.md'), dest)
|
|
105
|
+
written.push('~/.claude/skills/pre-commit-checks.md')
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Summary ───────────────────────────────────────────────────────────────────
|
|
110
|
+
console.log(`\n${PKG} init\n${'─'.repeat(40)}`)
|
|
111
|
+
if (written.length) {
|
|
112
|
+
console.log('Written:')
|
|
113
|
+
written.forEach((f) => console.log(` + ${f}`))
|
|
114
|
+
}
|
|
115
|
+
if (skipped.length) {
|
|
116
|
+
console.log('Skipped (already exist — pass --force to overwrite):')
|
|
117
|
+
skipped.forEach((f) => console.log(` · ${f}`))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log('\nManual steps:')
|
|
121
|
+
console.log(' 1. tsconfig.json — add:')
|
|
122
|
+
console.log(` { "extends": "${PKG}/typescript/nextjs" }`)
|
|
123
|
+
console.log(' 2. package.json — add for Prettier:')
|
|
124
|
+
console.log(` "prettier": "${PKG}/prettier"`)
|
|
125
|
+
console.log(' 3. Ensure husky is installed and "prepare": "husky" is in package.json scripts.')
|
|
126
|
+
console.log('')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { UserConfig } from '@commitlint/types'
|
|
2
|
+
|
|
3
|
+
const config: UserConfig = {
|
|
4
|
+
extends: ['@commitlint/config-conventional'],
|
|
5
|
+
rules: {
|
|
6
|
+
'type-enum': [
|
|
7
|
+
2, 'always',
|
|
8
|
+
['feat','fix','docs','style','refactor','perf','test','build','ci','chore','revert'],
|
|
9
|
+
],
|
|
10
|
+
'subject-empty': [2, 'never'],
|
|
11
|
+
'subject-full-stop': [2, 'never', '.'],
|
|
12
|
+
'subject-case': [2, 'always', 'lower-case'],
|
|
13
|
+
'type-case': [2, 'always', 'lower-case'],
|
|
14
|
+
'header-max-length': [2, 'always', 100],
|
|
15
|
+
'body-leading-blank': [1, 'always'],
|
|
16
|
+
'footer-leading-blank':[1, 'always'],
|
|
17
|
+
'scope-case': [2, 'always', 'lower-case'],
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default config
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared commitlint config for FinanceBuddha repos.
|
|
3
|
+
*
|
|
4
|
+
* Importable (no TypeScript loader required). In your commitlint.config.js:
|
|
5
|
+
* export { default } from '@financebuddha/standards/commitlint'
|
|
6
|
+
*
|
|
7
|
+
* Or extend it in commitlint.config.ts:
|
|
8
|
+
* import base from '@financebuddha/standards/commitlint'
|
|
9
|
+
* export default { ...base }
|
|
10
|
+
*
|
|
11
|
+
* The annotated TypeScript source of record lives in ./config.ts.
|
|
12
|
+
*/
|
|
13
|
+
const config = {
|
|
14
|
+
extends: ['@commitlint/config-conventional'],
|
|
15
|
+
rules: {
|
|
16
|
+
'type-enum': [
|
|
17
|
+
2,
|
|
18
|
+
'always',
|
|
19
|
+
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert'],
|
|
20
|
+
],
|
|
21
|
+
'subject-empty': [2, 'never'],
|
|
22
|
+
'subject-full-stop': [2, 'never', '.'],
|
|
23
|
+
'subject-case': [2, 'always', 'lower-case'],
|
|
24
|
+
'type-case': [2, 'always', 'lower-case'],
|
|
25
|
+
'header-max-length': [2, 'always', 100],
|
|
26
|
+
'body-leading-blank': [1, 'always'],
|
|
27
|
+
'footer-leading-blank': [1, 'always'],
|
|
28
|
+
'scope-case': [2, 'always', 'lower-case'],
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default config
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared ESLint flat config for FinanceBuddha Next.js projects.
|
|
3
|
+
*
|
|
4
|
+
* Usage in eslint.config.mjs:
|
|
5
|
+
* import config from './node_modules/@financebuddha/standards/eslint/nextjs.mjs'
|
|
6
|
+
* export default config
|
|
7
|
+
*/
|
|
8
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
9
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
10
|
+
import nextTs from "eslint-config-next/typescript";
|
|
11
|
+
|
|
12
|
+
export default defineConfig([
|
|
13
|
+
...nextVitals,
|
|
14
|
+
...nextTs,
|
|
15
|
+
globalIgnores([
|
|
16
|
+
".next/**",
|
|
17
|
+
"out/**",
|
|
18
|
+
"build/**",
|
|
19
|
+
"next-env.d.ts",
|
|
20
|
+
"scripts/**",
|
|
21
|
+
"skills/**",
|
|
22
|
+
"coverage/**",
|
|
23
|
+
]),
|
|
24
|
+
]);
|
package/husky/commit-msg
ADDED
package/husky/pre-commit
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
|
|
3
|
+
# Resolve the repo root so the patterns file is found regardless of where the
|
|
4
|
+
# hook runs from. Prefer a project-root secrets-patterns.txt (written by
|
|
5
|
+
# `npx @financebuddha/standards init`), else fall back to the one shipped in
|
|
6
|
+
# node_modules.
|
|
7
|
+
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
|
|
8
|
+
PATTERNS="$ROOT/secrets-patterns.txt"
|
|
9
|
+
if [ ! -f "$PATTERNS" ]; then
|
|
10
|
+
PATTERNS="$ROOT/node_modules/@financebuddha/standards/secrets-patterns.txt"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Secrets scan on staged changes. Strip comment/blank lines from the patterns
|
|
14
|
+
# file first — grep -f treats every line (including '# AWS') as a regex, which
|
|
15
|
+
# would otherwise match the comments themselves.
|
|
16
|
+
if [ -f "$PATTERNS" ]; then
|
|
17
|
+
CLEAN=$(grep -vE '^[[:space:]]*#|^[[:space:]]*$' "$PATTERNS")
|
|
18
|
+
if [ -n "$CLEAN" ]; then
|
|
19
|
+
SECRETS=$(git diff --cached -U0 | grep -E "$CLEAN" 2>/dev/null)
|
|
20
|
+
if [ -n "$SECRETS" ]; then
|
|
21
|
+
echo "❌ Possible secrets detected in staged changes:"
|
|
22
|
+
echo "$SECRETS"
|
|
23
|
+
echo ""
|
|
24
|
+
echo "Review the matches above. If they are false positives, remove the"
|
|
25
|
+
echo "matching lines or run 'git commit --no-verify' to bypass (use sparingly)."
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Run lint-staged (use bunx if available, else npx)
|
|
32
|
+
if command -v bunx >/dev/null 2>&1; then
|
|
33
|
+
bunx lint-staged
|
|
34
|
+
else
|
|
35
|
+
npx lint-staged
|
|
36
|
+
fi
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@financebuddha/standards",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared engineering standards: ESLint, TypeScript, Prettier, commitlint, secrets scanning, and Claude skills",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/financebuddha/engineering-standards.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/financebuddha/engineering-standards#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/financebuddha/engineering-standards/issues"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
"./eslint/nextjs": "./eslint/nextjs.mjs",
|
|
23
|
+
"./typescript/nextjs": "./typescript/nextjs.json",
|
|
24
|
+
"./typescript/base": "./typescript/base.json",
|
|
25
|
+
"./prettier": "./prettier/config.json",
|
|
26
|
+
"./commitlint": "./commitlint/index.js",
|
|
27
|
+
"./secrets-patterns": "./secrets-patterns.txt",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"fb-standards": "bin/init.mjs"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"eslint",
|
|
36
|
+
"typescript",
|
|
37
|
+
"prettier",
|
|
38
|
+
"commitlint",
|
|
39
|
+
"skills",
|
|
40
|
+
"husky",
|
|
41
|
+
"secrets-patterns.txt"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"init": "node bin/init.mjs"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"eslint-config",
|
|
48
|
+
"tsconfig",
|
|
49
|
+
"prettier-config",
|
|
50
|
+
"commitlint-config",
|
|
51
|
+
"financebuddha",
|
|
52
|
+
"engineering-standards"
|
|
53
|
+
],
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"eslint": ">=9",
|
|
56
|
+
"eslint-config-next": ">=15",
|
|
57
|
+
"prettier": ">=3",
|
|
58
|
+
"typescript": ">=5"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"eslint": { "optional": true },
|
|
62
|
+
"eslint-config-next": { "optional": true },
|
|
63
|
+
"prettier": { "optional": true },
|
|
64
|
+
"typescript": { "optional": true }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# FinanceBuddha secret scan patterns
|
|
2
|
+
# Used by CI (grep -Ef secrets-patterns.txt) and the pre-commit-checks Claude skill.
|
|
3
|
+
# Each line is a regex matched against git diff output.
|
|
4
|
+
|
|
5
|
+
# AWS
|
|
6
|
+
AKIA[0-9A-Z]{16}
|
|
7
|
+
|
|
8
|
+
# OpenAI
|
|
9
|
+
sk-[A-Za-z0-9]{20,}
|
|
10
|
+
|
|
11
|
+
# GitHub tokens
|
|
12
|
+
ghp_[A-Za-z0-9]{36}
|
|
13
|
+
gho_[A-Za-z0-9]{36}
|
|
14
|
+
github_pat_[A-Za-z0-9_]{82}
|
|
15
|
+
|
|
16
|
+
# Slack
|
|
17
|
+
xox[baprs]-[A-Za-z0-9-]{10,}
|
|
18
|
+
|
|
19
|
+
# Private keys
|
|
20
|
+
-----BEGIN (RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY
|
|
21
|
+
|
|
22
|
+
# JWT (3-part base64url)
|
|
23
|
+
eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}
|
|
24
|
+
|
|
25
|
+
# Google API keys
|
|
26
|
+
AIza[0-9A-Za-z_-]{35}
|
|
27
|
+
|
|
28
|
+
# Google OAuth client ID
|
|
29
|
+
[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com
|
|
30
|
+
|
|
31
|
+
# Generic credential assignments (password/secret/key = "value")
|
|
32
|
+
(password|passwd|pwd|secret|api_key|apikey|api-key|auth_token|access_token|client_secret|private_key|encryption_key)\s*[:=]\s*["'][^"']{8,}["']
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pre-commit-checks
|
|
3
|
+
description: >
|
|
4
|
+
Run the full pre-commit quality gate: secrets scan → lint → typecheck → tests.
|
|
5
|
+
Auto-fixes lint errors where possible, diagnoses and fixes failing tests, then
|
|
6
|
+
reports a timed pass/fail summary. Use this skill before every commit or release,
|
|
7
|
+
when asked to "check everything", "run pre-commit", "make sure it's clean",
|
|
8
|
+
"ready to commit?", or any time you want to verify the codebase is in a shippable
|
|
9
|
+
state. Also use proactively after making multiple code changes in one session.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Pre-Commit Checks
|
|
13
|
+
|
|
14
|
+
Run every step in order. Do not skip ahead. A later step failing does not
|
|
15
|
+
excuse an earlier step — fix each issue in the step where it appears before
|
|
16
|
+
moving on.
|
|
17
|
+
|
|
18
|
+
## Step 1 — Detect the package manager and project root
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Prefer bun if present, otherwise npm/yarn
|
|
22
|
+
cd <project-root>
|
|
23
|
+
which bun && RUNNER="bun run" || RUNNER="npm run"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For the shield project the runner is always `bun run`.
|
|
27
|
+
|
|
28
|
+
## Step 2 — Secrets scan
|
|
29
|
+
|
|
30
|
+
Scan staged files for accidental secrets before any code reaches the remote.
|
|
31
|
+
Run all three checks; report every hit — do not stop at the first one.
|
|
32
|
+
|
|
33
|
+
### 2a — Blocked file types (binary / credential files that should never commit)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git diff --cached --name-only | grep -Ei \
|
|
37
|
+
'\.(pem|p8|p12|pfx|key|keystore|jks|cer|crt|der|ppk|asc)$|^\.env(\.|$)|id_rsa|id_ed25519|id_ecdsa|id_dsa' \
|
|
38
|
+
&& echo "BLOCKED FILES FOUND" || true
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Any match is an **automatic FAIL**. Instruct the user to `git reset HEAD <file>`
|
|
42
|
+
and add the pattern to `.gitignore` before proceeding.
|
|
43
|
+
|
|
44
|
+
### 2b — High-confidence secret patterns in staged content
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git diff --cached -U0 | grep -nE \
|
|
48
|
+
'AKIA[0-9A-Z]{16}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{36}|gho_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82}|xox[baprs]-[A-Za-z0-9-]{10,}|-----BEGIN (RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}|AIza[0-9A-Za-z_-]{35}|[0-9]+-[0-9A-Za-z_]{32}\.apps\.googleusercontent\.com' \
|
|
49
|
+
&& echo "POSSIBLE SECRETS FOUND" || echo "No high-confidence secrets detected"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Any match: surface it to the user verbatim, identify the file and line number
|
|
53
|
+
from the diff context, and **stop** — do not commit. The user must review and
|
|
54
|
+
either remove the secret or confirm it is a false positive (e.g. test fixture
|
|
55
|
+
with a clearly fake value like `sk-test-fake-key-for-unit-tests`).
|
|
56
|
+
|
|
57
|
+
### 2c — Generic high-entropy / common key patterns in staged content
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git diff --cached -U0 | grep -nEi \
|
|
61
|
+
'(password|passwd|pwd|secret|api_key|apikey|api-key|auth_token|access_token|client_secret|private_key|encryption_key)\s*[:=]\s*["'"'"'][^"'"'"']{8,}["'"'"']' \
|
|
62
|
+
&& echo "CREDENTIAL ASSIGNMENTS FOUND" || echo "No credential assignments detected"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Flag any non-test, non-example matches to the user. Matches in files under
|
|
66
|
+
`__tests__/`, `__mocks__/`, `*.test.*`, `*.spec.*`, or `*.example.*` may be
|
|
67
|
+
false positives — confirm with the user before blocking.
|
|
68
|
+
|
|
69
|
+
### 2d — .env files not listed in .gitignore
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git diff --cached --name-only | grep -E '^\.env' | while read f; do
|
|
73
|
+
grep -qxF "$f" .gitignore 2>/dev/null || grep -qxF "/$f" .gitignore 2>/dev/null || \
|
|
74
|
+
echo "WARNING: $f is staged but not in .gitignore"
|
|
75
|
+
done
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Any `.env*` file staged that is not covered by `.gitignore` is an **automatic FAIL**.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
**If the secrets scan is fully clean**, output:
|
|
83
|
+
```
|
|
84
|
+
✓ Secrets scan — no issues found
|
|
85
|
+
```
|
|
86
|
+
and continue to Step 3.
|
|
87
|
+
|
|
88
|
+
## Step 3 — Lint
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
time bun run lint 2>&1
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**If lint exits non-zero:**
|
|
95
|
+
1. Run `bun run lint:fix` to auto-fix what ESLint can.
|
|
96
|
+
2. Re-run `bun run lint` and capture the remaining output.
|
|
97
|
+
3. For any errors that lint:fix could not resolve, read the flagged files and
|
|
98
|
+
fix them manually (one at a time — don't batch-guess).
|
|
99
|
+
4. Re-run lint a final time to confirm exit 0.
|
|
100
|
+
|
|
101
|
+
**Common lint patterns in this project:**
|
|
102
|
+
- Unused imports → remove them
|
|
103
|
+
- `any` type → narrow to the real type or use `unknown`
|
|
104
|
+
- Missing return types on exported functions → add them
|
|
105
|
+
- React hooks dependency array warnings → fix the dependency, don't suppress
|
|
106
|
+
|
|
107
|
+
## Step 4 — Typecheck
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
time bun run typecheck 2>&1
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
TypeScript errors are **never suppressed with `@ts-ignore` or `as any`** unless
|
|
114
|
+
there is a pre-existing justification comment. Fix each error properly:
|
|
115
|
+
- Missing property → add it to the type or interface
|
|
116
|
+
- Wrong type → correct the calling code
|
|
117
|
+
- Module not found → check the import path / tsconfig paths
|
|
118
|
+
|
|
119
|
+
Re-run typecheck after every fix to confirm the error is gone, not shifted.
|
|
120
|
+
|
|
121
|
+
## Step 5 — Tests
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
time bun run test 2>&1
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**If tests fail:**
|
|
128
|
+
1. Read the full failure output — identify the exact test name and file.
|
|
129
|
+
2. Open the test file and read the failing assertion.
|
|
130
|
+
3. Open the production file it is testing and understand why the behaviour
|
|
131
|
+
changed.
|
|
132
|
+
4. Fix the production code **or** update the test (update tests only when the
|
|
133
|
+
behaviour change was intentional — new feature, intentional refactor).
|
|
134
|
+
5. Re-run `bun run test` to confirm all tests pass.
|
|
135
|
+
|
|
136
|
+
Never delete or skip a test just to make the suite green — diagnose and fix the
|
|
137
|
+
underlying issue.
|
|
138
|
+
|
|
139
|
+
## Step 6 — Final summary
|
|
140
|
+
|
|
141
|
+
After all checks pass, output a summary block like this:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
✅ Pre-commit checks passed
|
|
145
|
+
─────────────────────────────────────
|
|
146
|
+
secrets ✓ clean
|
|
147
|
+
lint ✓ 1.2s
|
|
148
|
+
typecheck ✓ 4.7s
|
|
149
|
+
tests ✓ 3.6s 559 passed
|
|
150
|
+
─────────────────────────────────────
|
|
151
|
+
Total ✓ 9.5s
|
|
152
|
+
Ready to commit.
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
If any step cannot be fixed within a reasonable number of attempts (3–4 tries),
|
|
156
|
+
stop and surface the remaining error to the user with a clear description of
|
|
157
|
+
what is failing and why, rather than looping forever.
|
|
158
|
+
|
|
159
|
+
## Scope note
|
|
160
|
+
|
|
161
|
+
This skill covers **pre-commit quality gates only**. It does not:
|
|
162
|
+
- Bump version numbers
|
|
163
|
+
- Create git commits or tags
|
|
164
|
+
- Push to remote
|
|
165
|
+
|
|
166
|
+
For releasing, use `bun run release` (patch), `bun run release:minor`, or
|
|
167
|
+
`bun run release:major` after this skill exits successfully.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "FinanceBuddha Base",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ES2017",
|
|
6
|
+
"lib": ["esnext"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"module": "esnext",
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"emitDecoratorMetadata": true
|
|
17
|
+
},
|
|
18
|
+
"exclude": ["node_modules", "coverage"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "FinanceBuddha Next.js",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ES2017",
|
|
6
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"module": "esnext",
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"incremental": true,
|
|
18
|
+
"experimentalDecorators": true,
|
|
19
|
+
"emitDecoratorMetadata": true,
|
|
20
|
+
"plugins": [{ "name": "next" }],
|
|
21
|
+
"paths": { "@/*": ["./*"] }
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"next-env.d.ts",
|
|
25
|
+
"**/*.ts",
|
|
26
|
+
"**/*.tsx",
|
|
27
|
+
".next/types/**/*.ts",
|
|
28
|
+
".next/dev/types/**/*.ts",
|
|
29
|
+
"**/*.mts"
|
|
30
|
+
],
|
|
31
|
+
"exclude": ["node_modules", "scripts", "coverage"]
|
|
32
|
+
}
|