Add ARCHITECTURE.md with comprehensive codebase map covering service boundaries, expression system, session engine, sandbox, database layer, and architectural invariants. Add AGENTS.md as a concise guide for AI coding agents. Expand CONTRIBUTING.md with detailed code style, naming, import, testing, and git workflow conventions.
13 KiB
Contributing to GenieACS
Questions and Support
Please use the forum for questions, help requests, and general discussion. GitHub Issues are reserved for confirmed bug reports.
Issues
We use GitHub Issues to track bugs. When filing a bug report:
- Provide a clear description of the problem.
- Include steps to reproduce the issue.
- Note the GenieACS version, Node.js version, and MongoDB version.
- Include relevant log output if applicable.
If you face interoperability issues with a CPE, it is more often than not a device-specific issue. Please consult the forum before opening an Issue.
Pull Requests
Pull requests are welcome. For bug fixes, go ahead and open a PR directly. For new features or significant changes, please discuss in the forum first to ensure alignment with the project's direction.
When submitting a PR:
- Keep changes focused. One PR should address one concern.
- Run
npm run lintandnpm testbefore submitting. - Update documentation in
docs/if your change affects user-facing behavior. - Add tests where appropriate (see Testing).
- Write a clear PR description explaining what the change does and why.
Code Style
Naming Conventions
- Variables and functions:
camelCase - Classes:
PascalCase - Interfaces and type aliases:
PascalCase, without anIorTprefix - Module-level constants:
UPPER_SNAKE_CASE - Private class members: prefixed with underscore (
_name,_cache)
Choose descriptive, meaningful names. A longer clear name is better than a short ambiguous one.
Imports
Organize imports in three groups, in this order:
- Node.js built-in modules, using the
node:prefix - External packages
- Local project modules
Use .ts extensions for all local imports.
import { readFileSync } from "node:fs";
import * as http from "node:http";
import { Collection } from "mongodb";
import { connect, disconnect } from "./db.ts";
import Path from "./common/path.ts";
Functions and Exports
Use the function keyword for all named and exported function declarations.
Arrow functions should only be used for callbacks and short inline expressions.
// Named/exported functions use function keyword
export function processRequest(req: Request): Response {
// ...
}
// Arrow functions for callbacks
items.filter((item) => item.active);
promise.then((result) => {
// ...
});
TypeScript
- All function declarations must have explicit return types. This is enforced by ESLint.
- Using
anyis permitted where full typing would be impractical, but prefer more specific types when reasonable. - Prefer
Record<string, T>over{ [key: string]: T }for mapped types. - Use type assertions (
as) sparingly and only when you have stronger type knowledge than the compiler.
Comments
Code should be self-documenting. Use comments sparingly and only when the "why"
is not obvious from the code itself. When you do comment, use inline // style.
Do not use JSDoc (/** */) or block comments (/* */).
// Good: explains a non-obvious reason
// Escapes everything except alphanumerics and underscore
function escapeRegExp(str: string): string {
/* ... */
}
// Good: links to an external reference
// Source: http://stackoverflow.com/a/6969486
// Good: flags a known limitation
// TODO support "MD5-sess" algorithm directive
// Bad: restates what the code does
// Loop through each item and increment the counter
for (const item of items) {
counter++;
}
Error Handling
Use == null (not === null || === undefined) for null/undefined checks.
ESLint is configured to allow this.
Testing
When to Write Tests
Tests are most valuable for pure logic: parsers, data transformations, algorithms, and utility functions where inputs and outputs are well defined. A regression test accompanying a bug fix is also worthwhile to prevent the same issue from resurfacing.
Not every change needs a test. Use judgment and consider the likelihood and cost of breakage. Avoid writing tests that duplicate coverage already provided by existing tests, and resist the urge to test trivial code just for the sake of coverage numbers.
Conventions
Tests use the Node.js built-in test runner (node:test) and assertion module
(node:assert). No external test libraries or mocking frameworks are used.
import test from "node:test";
import assert from "node:assert";
void test("parseValue returns correct type for integer strings", () => {
const result = parseValue("42");
assert.strictEqual(result, 42);
});
void test("parseValue throws on invalid input", () => {
assert.throws(() => parseValue(""), new Error("empty value"));
});
Key conventions:
- Prefix
test()calls withvoidto satisfy theno-floating-promiseslint rule. - Keep tests flat. Do not nest
describeblocks. - Use descriptive test names that state what is being tested and the expected outcome.
- Use
assert.strictEqual()for value comparisons andassert.deepStrictEqual()for objects and arrays.
Test files live in the test/ directory and are named after the module they
test (e.g. test/path.ts tests lib/common/path.ts).
Dependencies
This project deliberately keeps its dependency footprint small. Before adding a new dependency:
- Prefer Node.js built-in APIs when they can do the job.
- Consider whether the functionality is simple enough to implement directly.
- Justify the addition in your PR description.
Do not add development tool dependencies (linter plugins, editor integrations, etc.) without prior discussion.
File Organization
| Directory | Contents |
|---|---|
lib/ |
Core server-side application code |
lib/common/ |
Code shared between server and browser |
lib/db/ |
Database layer (MongoDB) |
lib/ui/ |
UI backend helpers |
ui/ |
Frontend code (Mithril.js SPA) |
bin/ |
Service entry points |
build/ |
Build tooling |
test/ |
Test files |
docs/ |
User documentation (Sphinx/reStructuredText) |
public/ |
Static assets (favicon, logo) |
Place new code in the directory that matches its purpose. Server-side logic
belongs in lib/, code that must run in both Node.js and the browser belongs in
lib/common/, and frontend-only code belongs in ui/.
Documentation
User documentation lives in the docs/ directory as reStructuredText files
built with Sphinx and published to
docs.genieacs.com.
When your change affects user-facing behavior:
- Update the relevant documentation in
docs/. - If adding a new feature, consider whether it warrants a new page or a section in an existing page.
- Keep documentation concise and practical. Match the existing tone: direct, factual, no filler.
Documentation changes should be included in the same PR as the code change, not submitted separately.
ARCHITECTURE.md
ARCHITECTURE.md describes the high-level architecture of the project: the
service boundaries, major subsystems, data flow, and key invariants. It is aimed
at contributors who need a mental map of the codebase.
This file has a different update cadence than docs/. It should be revisited a
few times a year rather than kept in lockstep with every code change. When you
do update it, follow these principles:
- Only describe things that are unlikely to change frequently. Module responsibilities, service boundaries, key data structures, and architectural invariants belong here. Implementation details, function signatures, and config option lists do not.
- Name important files, modules, and types but do not link them. Links go stale. Encourage the reader to use symbol search to find named entities; this also helps them discover related, similarly named things.
- Keep it short. Every recurring contributor will read it. A shorter document is less likely to become stale and more likely to be maintained.
- Describe the "what" and "where", not the "how". This is a map of the country, not an atlas of its states. Pull detailed explanations into inline code comments or separate documents.
- Call out architectural invariants explicitly. Important invariants are often expressed as the absence of something (e.g., "the expression system does not depend on service-specific code") and are hard to discover by reading code alone.
Git Workflow
Commit Messages
This project follows the Conventional Commits format:
<type>(<scope>): <subject>
[optional body]
- Use the imperative mood, present tense: "Fix bug", not "Fixed bug" or "Fixes bug".
- Do not capitalize the first letter of the subject (the type prefix handles visual structure).
- Do not end the subject line with a period.
- Keep the subject line under 72 characters.
- When more context is helpful, add a body separated from the subject by a blank line, wrapped at 72 characters. The goal is to provide enough information for someone scanning the commit history to find a specific change (e.g. for troubleshooting or rebasing) or to draft changelog entries for a release. Don't be verbose, but don't be cryptic either.
Types
| Type | When to use |
|---|---|
fix |
Bug fixes |
feat |
New features or capabilities |
refactor |
Code restructuring with no behavior change |
test |
Adding or updating tests |
docs |
Documentation changes (docs/, CONTRIBUTING.md, README.md) |
build |
Build system, dependencies, esbuild config, package.json scripts |
chore |
Maintenance tasks that don't fit above (.gitignore, tooling config, etc.) |
Scopes
Scope is optional. Use it when it adds useful context; omit it when the change is cross-cutting or the subject already makes it obvious.
| Scope | Covers |
|---|---|
cwmp |
CWMP service (including extensions) |
nbi |
Northbound REST API service |
fs |
File service |
ui |
Web UI (frontend and backend) |
db |
Database layer |
If a change touches multiple scopes, either pick the primary one or omit the scope entirely.
Examples
feat(nbi): add bulk device delete endpoint
fix(cwmp): handle missing ParameterKey in InformResponse
refactor(db): replace raw queries with parameterized calls
fix(ui): correct parameter table sort order
test: add XML parser edge case coverage
docs: update provisioning guide for new API
build: upgrade esbuild to v0.20
fix(cwmp): increase server timeout to 2 mins
To allow enough time for running unindexed queries in large
deployments.
Branches
masteris the main development branch.- Create a feature or fix branch for your work and open a PR against
master. - Use concise, descriptive branch names in lowercase with hyphens:
fix-race-condition,support-xmpp-requests.
Changelog
The changelog (CHANGELOG.md) is maintained for each release and is written for
users and system administrators, not developers.
- Write entries as clear, user-facing prose. Do not copy commit messages verbatim.
- Each entry should describe what changed and, when helpful, why it matters.
- Group entries under a version heading with the release date:
## 1.2.13 (2024-06-06). - Start each entry with a verb: "Fix", "Add", "Improve", "Remove", etc.
- Include enough context that a user can understand the impact without reading the code.
You do not need to update the changelog in your PR. The maintainer will add changelog entries during the release process.
Contributor License Agreement
By submitting a pull request to this repository, you acknowledge that, while maintaining copyright, you grant GenieACS Inc. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute your contributions and such derivative works under the AGPLv3 license or any other license terms, including, but not limited to, proprietary or commercial license terms.
You confirm that you own or have rights to distribute and sublicense the source code contained therein, and that your content does not infringe upon the intellectual property rights of a third party.