Code standards
AlignTrue maintains consistent patterns across packages to improve maintainability and reduce duplication.
Shared utilities from @aligntrue/schema
The schema package exports high-quality utilities that should be used throughout the codebase to maintain consistency.
Hashing and cryptography
Use computeHash() instead of raw createHash():
// ❌ Don't: raw crypto operations scattered across code
import { createHash } from "crypto";
const hash = createHash("sha256").update(content).digest("hex");
// ✅ Do: use centralized utility
import { computeHash } from "@aligntrue/schema";
const hash = computeHash(content);Benefits:
- Single source of truth for cryptographic operations
- Easier to update algorithm if needed
- Consistent across all packages
- Already handles encoding/formatting
Available functions:
computeHash(data: string): string- SHA-256 hashcomputeContentHash(obj: unknown, excludeVolatile = true): string- Canonicalize (dropsvendor.*.volatileby default) + SHA-256hashObject(obj: unknown): string- Convenience wrappercomputeAlignHash(input: string | unknown): string- Resetsintegrity.valueto<pending>, filters volatile vendor fields, then hashes
Locations where hashing is used:
- File checksum computation (
file-utils) - Git repository hashing (
sources) - Content change detection (
core/tracking) - Section hashing in sync operations
JSON utilities
Use cloneDeep() instead of JSON.parse(JSON.stringify()):
// ❌ Don't: structural cloning anti-pattern
const clone = JSON.parse(JSON.stringify(obj));
// ✅ Do: use native structuredClone with fallback
import { cloneDeep } from "@aligntrue/schema";
const clone = cloneDeep(obj);Benefits:
- Uses native
structuredClone()for better performance - Handles more types (Date, Map, Set, etc.)
- Explicit intent in code
- Fallback for older environments (though not needed for Node 20+)
Available functions:
cloneDeep<T>(obj: T): T- Deep clone using structuredCloneparseJsonSafe(str: string): Result<unknown, Error>- Parse with error handlingstringifyCanonical(obj: unknown, excludeVolatile = true): string- Canonical JSON (dropsvendor.*.volatileby default)computeContentHash(obj: unknown, excludeVolatile = true): string- Deterministic hash (canonical JSON + SHA-256)compareCanonical(a: unknown, b: unknown): boolean- Compare by canonical formtype Result<T, E>- Result type for operations that may fail
Locations where JSON utilities are recommended:
- Object cloning in overlay operations
- Align hashing before resolution
- Type-safe parsing of untrusted JSON
Canonicalization
Use canonicalizeJson() and computeAlignHash() for deterministic operations (defaults exclude vendor.*.volatile):
// ✅ Do: use schema utilities for determinism
import { canonicalizeJson, computeAlignHash } from "@aligntrue/schema";
const canonical = canonicalizeJson(obj); // volatile fields dropped by default
const hash = computeAlignHash(yamlString); // sets integrity.value to <pending> before hashingError handling
CLI errors
Use the structured error hierarchy (re-exported by @aligntrue/cli/utils/error-types, defined in @aligntrue/core) so exit codes, hints, and next steps stay consistent:
import {
AlignTrueError,
ConfigError,
ValidationError,
SyncError,
ErrorFactory,
} from "@aligntrue/cli/utils/error-types";
// Create descriptive errors with actionable guidance
throw new ConfigError(
"Invalid config field: profile.id missing",
"Set profile.id in .aligntrue/config.yaml",
).withNextSteps(["Run: aligntrue init", "Edit: aligntrue config edit"]);
// Prefer ErrorFactory helpers for common cases
throw ErrorFactory.configNotFound(configPath);
// Use AlignTrueError (base) only when none of the typed errors fit
throw new AlignTrueError(
"Unexpected state while resolving plugs",
"UNEXPECTED_STATE",
1,
);Core package errors
Keep error messages clear and actionable. Use the typed errors above for user-facing failures; reserve bare Error for internal invariants only:
// ✅ Good (user-facing)
throw ErrorFactory.fileWriteFailed(path, cause);
// ✅ Good (internal invariant)
if (!graph.has(node)) {
throw new Error(`Invariant: node ${node} should exist before traversal`);
}
// ❌ Avoid
throw new Error("Config load failed");Testing patterns
Test organization
Mirror source layout in tests:
packages/core/
src/
sync/
engine.ts
config/
index.ts
tests/
sync/
engine.test.ts
config/
index.test.tsFixtures and factories
Create reusable test fixtures near complex logic:
// packages/core/tests/helpers/test-fixtures.ts
export function createMockConfig(
overrides?: Partial<AlignTrueConfig>,
): AlignTrueConfig {
return {
version: "1.0.0",
mode: "solo",
...overrides,
};
}Performance considerations
File operations
Use the centralized AtomicFileWriter from @aligntrue/file-utils:
import { AtomicFileWriter } from "@aligntrue/file-utils";
const writer = new AtomicFileWriter();
// Optional: prompt-aware checksum handler to protect manual edits
// writer.setChecksumHandler(async (...) => "overwrite" | "keep" | "abort");
await writer.write(filePath, content, { interactive, force });
// Handles atomicity, checksum tracking, overwrite protection, rollbackLarge datasets
The schema package provides performance guardrails:
import { checkFileSize } from "@aligntrue/core/performance";
checkFileSize(filePath, 100, "team", force);
// Solo: warns; Team/enterprise: throws; Force: bypassesPackage dependencies
Core package constraints
-
Core (
@aligntrue/core): No UI dependencies- Remove unused
@clack/prompts(only CLI needs this) - Focus on config, sync, and validation logic
- Remove unused
-
CLI (
@aligntrue/cli): Can depend on UI and prompts- Use
@clack/promptsfor interactive prompts - Depends on core for business logic
- Use
-
Schema (
@aligntrue/schema): No external business logic- Validation, hashing, canonicalization only
- Depended on by all other packages
Import order
Within files, use this order:
// 1. Standard library
import { promises as fs } from "fs";
import { join } from "path";
// 2. Third-party packages
import { parse } from "yaml";
// 3. Local workspace packages
import { computeHash } from "@aligntrue/schema";
import { loadConfig } from "./config/index.js";
// 4. Relative imports
import { helper } from "../utils/helper.js";Documentation
When to add documentation
- Add: Feature behavior, configuration options, CLI commands, public APIs
- Skip: Internal refactors, implementation details that don’t affect users
- Update: When user-facing behavior changes
Keep documentation current
Use the validation/docs check to ensure:
- Node.js version requirements match
package.json - CLI command counts match implementation
- Exporter counts match directory listing
pnpm validate:docsRelated documentation
- CI guide for validation and debugging workflow
- Architecture for design principles
- Test maintenance for test strategy