Contribute
Extending Construct
Add a rule, a layer template, an LLM provider or a command.
Construct is designed to be extended by adding small blocks, not by changing existing ones. Each recipe below names the real files involved; check Building blocks first in case one already exists.
Add a rule#
- Declare it in
DEFAULT_RULESinpackages/core/config.mjs:'MY-001': { severity: 'error', name: 'What it enforces' }. That makes it configurable inarchitecture.ymland gives it "did you mean" suggestions for typos. - Check it in the enforcer that owns the concern (
architecture-enforcer.mjs,soc-enforcer.mjsorreadability-enforcer.mjs). Read the configured severity fromconfig.rules[id], honouroffandexceptions:(the architecture enforcer'spushViolationdoes both), and report withmakeViolationfrompackages/core/diagnostics.mjs. Every violation needs awhyand, where possible,expectedandsuggestedFix: the message is aimed at a person or model who has to fix it. - Test it in
test/with a fixture project that violates the rule and one that does not. - List it: the rule reference is generated from
DEFAULT_RULES, so it appears automatically.
A whole new enforcer is one more { name, validate(root) -> { violations } } entry in packages/engine/defaultEnforcers.mjs; the registry merges the results.
Add or change a layer template#
Generated files come from templates in packages/core/generators.mjs. Each layer has a template, a folder, a file-name convention (layerFileBaseName) and a constraint string (LAYER_CONSTRAINTS) that is shown verbatim to any model asked to fill the file. Generation order is LAYER_ORDER.
A project can override a layer's template without touching Construct, using templates: { <layer>: <path> } in its architecture.yml. After changing a template, run the tests: generated code is validated against the enforcers and a template that produces an invalid file fails.
Add an LLM provider#
Providers live in the PROVIDERS map in packages/core/llm.mjs: a name mapped to async (prompt, options) => string. Add an entry and --llm <name> accepts it; nothing else changes, because every model call in Construct goes through callLlm. Throw ConstructError for failures. Replies written into files also pass through packages/core/llm-fill.mjs, which requires them to parse before they are written. Keep providers out of everything except this module so the rest of the tool stays repeatable.
Add a deterministic block#
Give it a single entry point, JSDoc, tests, and a row in docs/capabilities.md (which appears on the Building blocks page). If it reads or writes TypeScript/JSX, build on packages/ast rather than a new parser. If it must touch several files, stage them through createTransaction so a failure leaves nothing half-written.
Add a CLI command#
Commands are dispatched in packages/cli/construct.mjs to functions exported from packages/core/cli.mjs. Add the function, add its usage to packages/core/usage.mjs (the CLI reference is generated from it), and mention it in the README's command list.