Files
genieacs/CONTRIBUTING.md
T
Zaid Humaimidi bef840ce0a docs: add ARCHITECTURE.md and AGENTS.md, expand CONTRIBUTING.md
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.
2026-03-12 14:26:46 -07:00

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 lint and npm test before 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 an I or T prefix
  • 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:

  1. Node.js built-in modules, using the node: prefix
  2. External packages
  3. 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 any is 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 with void to satisfy the no-floating-promises lint rule.
  • Keep tests flat. Do not nest describe blocks.
  • Use descriptive test names that state what is being tested and the expected outcome.
  • Use assert.strictEqual() for value comparisons and assert.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

  • master is 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.