SKILL.md
When implementing JavaScript code, follow these guidelines.
Code format guide:
* Use tabs and double quotes for strings
* Make sure to follow style guide from eslint or/and editorconfig if present in the project
* Use Common.JS syntax for imports and exports
* I do not like to use this format const { recordEvent } = require("./event-log");, instead, do this const recordEvent = require("./event-log").recordEvent;
* Write exports directly in module.exports = {};
* Expose constants at the top of the file, after imports, for easier configuration
* Use JSDoc when function parameters are ambiguous (e.g., options objects with multiple properties)
* Prefer if/else over ternaries for non-trivial logic - readability over cleverness
Project structure
* Make sure to always create folder for each module, and create index.js inside, it helps with organization and reduces chance of conflict
* I like to keep code for talking to other services in src/services folder
* I like to keep helper code that is specification specific in src/utils folder. If it is not logically part of application, just something that is needed to make things work, it should go to utils
* I create entrypoints into project in bin/ folder (e.g., bin/pull.js, bin/sync.js)
All entrypoints must be: Started with forever (e.g., forever bin/pull.js) Added to package.json scripts (e.g., "pull": "forever bin/pull.js") Added to Procfile for herokuish (e.g., pull: yarn pull) Have a -watch suffix variant using nodemon for development (e.g., "pull-watch": "nodemon bin/pull.js") Naming convention: <name> for prod, <name>-watch for dev (suffix, not prefix)
* If nodemon is not in package.json, install it as dev dependency (yarn add -D nodemon)
* Scripts run in heroku/herokuish environment
Technical guidelines
* Use Promise instead of async/await for asynchronous operations
* Chain promises sequentially with .then() rather than nesting:
``javascript return doFirst() .then(() => { return doSecond(); }) .then(() => { return doThird(); }); ``
* Avoid using ES6+ features that are not widely supported in Node.js environments
* I like functional JS, map/reduce/foreach/filter should be preferred over for loops
* I like to use wrappers for repeating code, like rate limiting, cachine, etc.
* Never use var, always const/let. Unless you really need to change value, use const
* Never change parameters of functions, always create new variables if you need to change something
* Handle errors at the last possible moment to avoid losing error data when rebuilding. Catch earlier only when it makes sense for the logic
Architectural guidelines
* Try to encapsulate complex logic in modules and expose logic so that final code looks almost like pseudocode
* Name modules based off their functionality, not how they are implemented (for example, module for logging stuff to mongo should be called logger, not mongo)
* Where it makes sense, if there is array getter, also implement getter that returns map, calling original getter and transforming result to map
Service pattern
* Services should be defined as objects with methods, not separate functions
* Use _client for the HTTP client (private by convention), do not export it
* Reference the service object by name inside methods
* Example:
```javascript const got = require("got-verbose"); const config = require("../../config");
const myService = { client: got.extend({ prefixUrl: "https://api.example.com", headers: { authorization: "Bearer " + config.get("MYAPI_KEY") } }),
getItems: () => { return myService._client.get("items").then((response) => { return JSON.parse(response.body).items; }); },
getItemsMap: () => { return myService.getItems().then((items) => { const map = {}; items.forEach((item) => { map[item.id] = item; }); return map; }); } };
module.exports = myService; ```
Package manager
* Always use yarn instead of npm, unless there is a package-lock.json present (then use npm)
* Never guess npm package versions — use yarn add <package> (or yarn add -D <package> for dev dependencies) and let yarn resolve the correct version. Do not manually edit dependency versions in package.json.
Version control
* Make sure there is .gitignore file present
* Make sure to ignore node_modules
Linting & Code Quality
* Use ESLint with eslint:recommended and @babel/eslint-parser
ESLint config (.eslintrc.json): indent: tabs quotes: double semi: always no-param-reassign: error no-trailing-spaces: error no-unused-vars: error (with argsIgnorePattern: "^_") env: es6, node, commonjs, plus test env (jest or mocha)
* Dev dependencies for linting: eslint, @babel/core, @babel/eslint-parser
* Add "lint": "eslint src/ bin/" script (and tests/ if present)
Testing
* Use jest for testing (with supertest for API tests)
* Jest config in package.json: "jest": { "testEnvironment": "node", "forceExit": true }
* Test files go in tests/ folder inside each module (e.g., src/config/tests/config.test.js)
* Add scripts: "test": "jest", "test:watch": "jest --watch"
Pre-commit hooks
* Use husky (v9+) with lint-staged for pre-commit hooks
* Dev dependencies: husky, lint-staged
* Add "prepare": "husky" script to package.json
Add lint-staged config to package.json: "lint-staged": { ".js": "eslint --fix" }
* .husky/pre-commit should run: `` yarn lint-staged yarn test ``
Libraries and tools
* Lib I like to use for rate limiting (how many calls of method should happen at the same time) is queue-promised
* Install it, package name is queue-promised
* Import method wrapper from queue-promised (const wrapper = require("queue-promised").wrapper;)
* Use it like this const limitedFunction = wrapper(originalFunction, 5); where 5 is how many calls can happen at the same time
* I like to use uuid package for generating uuids. I like to use v4 method from it. and I import it like this: const uuid = require("uuid").v4;
* I like to use node-cron for cron jobs. I import it like this: const cron = require("node-cron");
* I like to use got-verbose, which is a wrapper around got library for HTTP requests. It exposes identical API as got, but has built-in logging and error handling. I import it like this: const got = require("got-verbose");
* Note: got-verbose does not work with streams. For streaming, use got directly (const got = require("got");)
* Avoid using .json() method of got-verbose and json option, instead use .body and parse JSON manually, it does have some unexpected behavior
* I like to use forever for running Node.js applications. Use it in the start script: "web": "forever src"
* The web entry point is always src/index.js (not in bin/). Other processes (worker, cron, etc.) go in bin/ with .js extension
* Script naming: web/watch for web server, worker/watch-worker for workers, etc. (not start/dev)
Claude notice
* Add note to CLAUDE.md that this skill should be always used
* Suggest changes to this skill, when I suggest code style changes
* If you notice a pattern in my instructions, suggest adding it to this skill
* Try to keep CLAUDE.md in sync with project changes
* If you are writing a pseudocode (calling method or API endpoint you are not sure exists), add TODO comment so I know I should fix it
Building container
It is usual for me to use herokuish build like this
FROM gliderlabs/herokuish:latest
COPY . /app
WORKDIR /app
RUN /bin/herokuish buildpack build
ENV PORT 3000
EXPOSE 3000
Cleanup time
When I tell you it is cleanup time, follow this checklist:
* /implement-js
* update docs (README.md)
* update your docs (CLAUDE.md)
* update missing jsdoc...
* check tests
* check linter