IMPORTANT: On this system, node -e with multi-line inline scripts causes the terminal to hang indefinitely even after the command finishes. This is a known Antigravity bug on Linux.
Rules:
- Never use
node -e "..."with multi-line JavaScript/TypeScript. - Instead, write the script to a temp file and run it:
cat > /tmp/script.ts << 'EOF' // your code here EOF npx tsx /tmp/script.ts
- For simple one-liners, prefer
npm run testor other package scripts. - Always set
WaitMsBeforeAsyncappropriately — use short timeouts (500-2000ms) for commands that might hang, and check withcommand_status. - Build commands (
npm run build,nx run, etc.) hang indefinitely when there are lint or compilation errors. Always use a shortWaitMsBeforeAsync(e.g., 500ms) and monitor withcommand_status, or pipe throughtimeout 30to force termination. - Command Canceled: Always query the output of a command using
command_statuswithWaitDurationSeconds: 0even if its status was previously reported asCANCELEDduring a poll. A canceled command might still have produced useful error output before being terminated. - Apparent hangs: When a command appears to hang (stuck in RUNNING with no output), always assume it is the known Antigravity Linux terminal bug first, not an infinite loop in the code. Query
command_statuswithWaitDurationSeconds: 0andOutputCharacterCountto check if output was already produced. Do not prematurely terminate commands or assume code bugs without first verifying the output. - Test script placement: Never place test scripts that import project modules in
/tmp/. Scripts in/tmp/cannot resolve monorepo workspace imports (e.g.,@modelscript/core) or relative paths back into the repo. Always place ad-hoc test scripts inside the repo (e.g.,packages/core/tests/or a scratch file alongside the source). - Tree-sitter for Node.js: When writing scripts that run on desktop/Node.js (tests, CLI tools, etc.), always use the native
tree-sitterpackage — neverweb-tree-sitteror WASM. Follow the pattern inpackages/core/tests/jest.setup.ts:import Modelica from "@modelscript/tree-sitter-modelica"; import Parser from "tree-sitter"; import { Context } from "../src/compiler/context.js"; const parser = new Parser(); parser.setLanguage(Modelica); Context.registerParser(".mo", parser);
- SafeToAutoRun: Always set
SafeToAutoRuntofalsefor test scripts, debug scripts, and any command whose output you need to read. Setting it totruecauses the command to run in the background where it appears frozen. Only useSafeToAutoRun: truefor trivial commands likecat,ls, orechothat complete almost instantly.
IMPORTANT: Never use concrete syntax nodes (tree-sitter SyntaxNode) for flattening or interpretation logic. Concrete syntax nodes are ephemeral — they are only available during initial parsing and are NOT preserved through cloning, modification merging, or serialization.
Rules:
- Always use abstract syntax nodes (
ModelicaClassDefinitionSyntaxNode,ModelicaLongClassSpecifierSyntaxNode, etc.) for accessing class elements, sections, and other structural information in the flattener, interpreter, and model. - Never access
concreteSyntaxNodefields for semantic analysis. They may benullon cloned or deserialized instances. - Properties like
sections,elements,equations,statementsare populated at construction time from whichever source is available (concrete or abstract). After construction, always access them through the abstract syntax node wrappers.
When adding new linter rules to @modelscript/core, follow this pattern:
Add a new entry to ModelicaErrorCode in packages/core/src/compiler/modelica/errors.ts:
RULE_NAME: {
code: XXXX, // Unique numeric code (see numbering scheme below)
rule: "rule-name", // Kebab-case identifier
severity: "error", // "error" | "warning" | "info"
message: (param: string) => `Descriptive message with '${param}'.`,
},Error code numbering:
1xxx— Parser / Syntax2xxx— Name resolution3xxx— Type checking4xxx— Structural / Semantic5xxx— Equations & Algorithms
Add a ModelicaLinter.register(...) call at the bottom of packages/core/src/compiler/modelica/linter.ts:
ModelicaLinter.register(ModelicaErrorCode.RULE_NAME, {
visitClassInstance(node: ModelicaClassInstance, diagnosticsCallback: DiagnosticsCallbackWithoutResource): void {
// Guard: only apply to relevant class kinds
if (node.classKind !== ModelicaClassKind.FUNCTION) return;
// Detection logic...
diagnosticsCallback(
ModelicaErrorCode.RULE_NAME.severity,
ModelicaErrorCode.RULE_NAME.code,
ModelicaErrorCode.RULE_NAME.message(param),
syntaxNode, // AST node for source location
);
},
});Create a .mo file in the appropriate testsuite/OpenModelica/flattening/ subdirectory.
- For correct tests (
// status: correct): the// Result:block contains the expected flattened output. - For incorrect tests (
// status: incorrect): the// Result:block contains the expected diagnostic output in the format:// [relative/path/to/file.mo:startLine:startCol-endLine:endCol] Severity: [MXXXX] Message text.
npm run build --workspace=@modelscript/core # Compile + lint
npm run test --workspace=@modelscript/core # Run test suite| Code | Rule | Description |
|---|---|---|
| M1001 | parser-error |
Syntax errors detected by tree-sitter |
| M2001 | unresolved-reference |
Reference to undefined name |
| M3001 | type-mismatch |
General type incompatibility |
| M3006 | function-arg-type-mismatch |
Function argument type mismatch |
| M3007 | function-return-type-mismatch |
Function return type mismatch |
| M3009 | array-index-type-mismatch |
Array index must be Integer or Boolean |
| M4001 | extends-cycle |
Circular extends chain |
| M4002 | duplicate-modification |
Same element modified twice |
| M4003 | array-dimension-mismatch |
Array shape mismatch |
| M4004 | unbalanced-model |
Equation/variable count mismatch |
| M4007 | function-public-variable |
Non-input/output public variable in function |
| M4008 | array-subscript-count-mismatch |
Wrong number of array subscripts |
| M4009 | function-default-arg-cycle |
Cyclic dependency in function default arguments |
| M5001 | equation-type-mismatch |
Type mismatch in equations |
| M5002 | constrainedby-type-mismatch |
Replaceable type constraint violation |
IMPORTANT: The testsuite runner for @modelscript/core is located at packages/core/tests/testsuite-runner.ts (NOT src/test/). It does not support --filter. Arguments are subdirectory names or .mo file paths relative to the testsuite/ root.
Run commands (from the monorepo root):
# Run all tests
npm run test --workspace=@modelscript/core
# Run a specific subdirectory (e.g., all "types" tests)
cd packages/core && npx tsx tests/testsuite-runner.ts OpenModelica/flattening/modelica/types
# Run a single test file
cd packages/core && npx tsx tests/testsuite-runner.ts OpenModelica/flattening/modelica/types/IntegerToEnumeration.mo
# Update expected output to match actual output (rewrites // Result: blocks)
cd packages/core && npx tsx tests/testsuite-runner.ts --update OpenModelica/flattening/modelica/types/IntegerToEnumeration.moRules:
- Never use
--filter— it does not exist and the argument will be interpreted as a path. - Arguments are relative to
packages/core/testsuite/. UseOpenModelica/flattening/modelica/<subdir>for subdirectories. - Use
--updateto auto-rewrite the// Result:block in.mofiles to match actual output. - Pipe through
timeout 60to guard against hangs:timeout 60 npx tsx tests/testsuite-runner.ts ...
IMPORTANT: The linter and the flattener must always be in sync. Do not move diagnostics or features from the linter to the flattener (or vice versa) simply to work around implementation difficulties.
Rules:
- Keep diagnostics where they belong: If a diagnostic is implemented as a linter rule in ModelScript (analogous to OMC's frontend semantic checks), it must remain in the linter.
- Fix the root cause: If the linter lacks context that the flattener has (e.g., proper state management or hierarchy traversal), fix the linter's implementation rather than moving the check to the flattener.
IMPORTANT: When a test fails because of a diagnostic mismatch, always fix the code to produce the same error message as OpenModelica. The .mo test expected output reflects the correct OMC behavior and should be treated as the source of truth for error messages.
Rules:
- If a test fails because the error message text differs from the expected output, update the error message in the ModelScript code (e.g., in
errors.ts,flattener.ts, orlinter.ts) to match OpenModelica's format. - If a test fails only because of a line/column range mismatch in the diagnostic output, update the
.motest file's expected// Result:block to use the line ranges that ModelScript produces — only when it is difficult to make ModelScript produce the exact same ranges.
IMPORTANT: Do not reorder components (variables, protected declarations, etc.) in .mo test expected output to match ModelScript's current ordering. The expected output reflects the correct ordering from OpenModelica.
Rules:
- If a test fails only because components appear in a different order, fix the flattener/DAE printer to emit components in the correct order, matching the test's expected output.
- Never swap, reorder, or rearrange component declarations in test
// Result:blocks to work around ordering bugs in the flattener.
IMPORTANT: Never run the git commit command yourself. The user will handle committing the code.
When generating git commit messages (including via the "generate git commit" button), always use Conventional Commits format:
Rules:
-
Never run
git commit. Always let the user run the commit command. -
Never commit and/or push changes without explicit authorization.
-
Auto-generate commit messages. After every successful fix or change, proactively generate a commit message without being asked.
-
Use conventional commit format —
type: description. -
Valid types:
feat,fix,refactor,chore,docs,build,ci,perf,test,style. -
All lowercase — no capitalization in description, no title case.
-
No trailing period — the message is a phrase, not a sentence.
-
Use scope when relevant —
type(scope): description, e.g.fix(core): ...,feat(cli): .... -
Comma-separated list for multiple changes in description — group related changes.
-
Be terse but descriptive — describe what changed, not why.
Examples:
fix(core): nested array toJSON serialization, refactor annotation clause merging
feat(cli): add partial function application support
fix: 2D array equations, refactor built-in function dispatch to metadata-driven tables
ci: add gpg commit signing to release workflow for verified commits
chore: use wildcard dependency ranges for internal workspace packages
docs: add security policy and vulnerability reporting guidelines