go-append-slice
internal/store/scores.go:6
package store
func growScores(scores []int) []int {
out := make([]int, 0, len(scores))
for _, score := range scores {
out = append(out, score)
}
return out
}Policy as code
Your AI agent can forget instructions and conventions. CodeRuler never forgets the rules you save.
Policy intent
When the output count is known, allocate the slice at its final length.
Steps
The **/*.go pattern selects Go files, including internal/store/scores.go below.
The selector finds functions and methods with a for loop that contains append. The AI checks each match.
The AI receives the selected function. It can compare the loop's output count with the slice length and decide whether to fill the slice directly.
Run from your repository root:
coderuler checkimport { Rule, ai, scopes } from '@alex-dbs/coderuler';
export default class GoAppendSlice extends Rule {
enabled = false;
files = ['**/*.go'];
scope = scopes.astGrep({
language: 'go',
rule: {
any: [{ kind: 'function_declaration' }, { kind: 'method_declaration' }],
has: {
kind: 'for_statement',
stopBy: 'end',
has: {
pattern: 'append($SLICE,$$$ARGS)',
stopBy: 'end',
},
},
},
});
check = ai({
instruction: `For a fresh slice allocated for a loop with a known output count, allocate its final length and fill it by index or copy.
Keep append when the final count is unknown before the loop or when the slice has a deliberately assigned prefix.
Do not allocate elements that the loop then appends instead of filling them.`,
});
}Read the full source policy internal/store/scores.go:6
package store
func growScores(scores []int) []int {
out := make([]int, 0, len(scores))
for _, score := range scores {
out = append(out, score)
}
return out
}From source to a decision
Follow the rule on the left as its source moves through each step on the right.
Check each Go pointer for nil on every path before dereferencing it. Check every nested pointer in the field path. This policy applies to explicit dereferences, not ordinary field access.
import { Rule, ai, scopes, type ScopeSubject } from '@alex-dbs/coderuler';
export default class GoDereferenceGuard extends Rule {
files = ['**/*.go'];
exclude = ['vendor/**'];
scope = scopes.astGrep({
language: 'go',
rule: {
any: [{ kind: 'function_declaration' },
{ kind: 'method_declaration' }],
has: {
kind: 'unary_expression',
stopBy: 'end',
has: { field: 'operator', regex: '^\\*$' },
},
},
});
filter = (subject: ScopeSubject) =>
!subject.filepath.endsWith('_test.go');
check = ai({
instruction: 'Check each Go pointer for nil on every path before dereferencing it. Check every nested pointer in the field path. This policy applies to explicit dereferences, not ordinary field access.',
violationMessage: 'Check each pointer for nil before using *.',
});
}Read the full source policy The AI checks each selected function for missing nil checks. It reads the Go file to find nested pointer types.
import { Rule, regex, scopes, type ScopeSubject } from '@alex-dbs/coderuler';
export default class ServiceProto extends Rule {
files = ['**/*.proto'];
exclude = ['vendor/**'];
scope = scopes.protobuf({
path: "$.rpcs[*].options['google.api.http']..['get','put','post','delete','patch']",
});
filter = (subject: ScopeSubject) =>
!subject.filepath.startsWith('proto/public/');
check = regex({
pattern: /^\/service\/v\d+\/[a-z][a-z0-9_]*s(?:\/.*)?$/,
violationMessage: 'Use /service/vX/ and a lowercase plural resource.',
});
}Read the full source policy Evaluate a regular expression locally against each selected HTTP URL. Public API files use a different URL policy, so the filter omits them.
import { Rule, scopes, pass, violation, type ScopeSubject } from '@alex-dbs/coderuler';
export default class SourceLineLimit extends Rule {
files = ['src/**/*.ts'];
exclude = ['src/vendor/**'];
scope = scopes.file();
filter = (subject: ScopeSubject) =>
!subject.filepath.endsWith('.test.ts');
check: Rule['check'] = subject => {
const lineCount = (subject as ScopeSubject).lines!.end;
return lineCount > 80
? violation('Keep source files at 80 lines or fewer.')
: pass();
};
}Open the custom-function docs Run custom JavaScript or TypeScript logic without a provider. This check counts the lines in each selected source file.
Include the paths you need. Exclude the rest.
src/config.goIncluded →vendor/cache.goExcluded ↘A scope selects a whole file or a source fragment.
package configfunc nestedValue(config *Config) int {
if config == nil {
return 0
}
return *(*config).Child.Value
}nestedValueKeep applicable matches. A filter does not decide pass or fail.
trueconfig.go → checkfalseconfig_test.go → skipEvaluate the retained context against the rule instruction.
func nestedValue(config *Config) int {
if config == nil {
return 0
}
return *(*config).Child.Value
}type Config struct { Child *Child }
type Child struct { Value *int }Check each Go pointer for nil on every path before dereferencing it. Check every nested pointer in the field path. This policy applies to explicit dereferences, not ordinary field access.
/^\/service\/v\d+\/[a-z][a-z0-9_]*s(?:\/.*)?$//service/v1/invoicespattern.test("/service/v1/invoices")export function total(items: number[]) { return items.length;}lineCount > 80violation(…)Check each pointer for nil before dereferencing it.
See how rules enforce policies in code and configuration.
131msto catch CWE-113: HTTP response splitting
at a cost of $0.0002
tests/manual/vulnerable_code.js:3
export function respond(request, socket) {
const name = new URL(request.url, 'http://localhost').searchParams.get('name');
socket.write('HTTP/1.1 200 OK\r\nX-Name: ' + name + '\r\n\r\n');
}import { Rule, ai, scopes } from '@alex-dbs/coderuler';
export default class HttpResponseSplitting extends Rule {
files = ['tests/manual/vulnerable_code.js'];
scope = scopes.astGrep({
language: 'javascript',
rule: {
any: [
{ kind: 'function_declaration' },
{ kind: 'function_expression' },
{ kind: 'arrow_function' },
{ kind: 'method_definition' },
],
},
});
check = ai({
// Abridged prompt. Full policy and tests in the source.
instruction: `Reject CR and LF in untrusted values before
inserting them into HTTP response headers or status lines.
This policy covers construction and writes visible in the selected
function. Hidden helper behavior and unseen callers are outside its
scope. Treat serialization arguments and statusMessage as untrusted.
Fixed values and values checked for both CR and LF meet this
requirement. Preserve fixed HTTP delimiters.`,
violationMessage: 'Reject CR and LF in attacker-controlled response headers and status messages before writing them.',
});
}Read the full source policy This function queries SQL through a concrete *sql.DB client inside internal/app.
Call a port from the application layer. Keep the SQL query in the adapter.
internal/app/account/query.go:12
package app
import (
"context"
"database/sql"
)
type Account struct { ID string }
func Find(ctx context.Context, db *sql.DB, id string) (Account, error) {
var account Account
err := db.QueryRowContext(ctx, "SELECT id FROM accounts WHERE id = ?", id).Scan(&account.ID)
return account, err
}import { Rule, ai, scopes } from '@alex-dbs/coderuler';
export default class GoAppNoInfrastructure extends Rule {
enabled = false;
files = ['internal/app/**/*.go'];
scope = scopes.astGrep({
language: 'go',
rule: { any: [{ kind: 'function_declaration' }, { kind: 'method_declaration' }] },
});
check = ai({
instruction: 'Keep each Go function in internal/app focused on application logic. Do not perform I/O with external systems through concrete clients in these functions; keep that work in adapters. A function may request external work through an injected interface (a port). Judge only the selected function.',
violationMessage: 'Move infrastructure I/O out of the domain and call an injected port instead.',
});
}Read the full source policy The code checks config for nil, but it does not check nested pointers Child or Value. This can cause nil pointer errors.
internal/store/config.go:8
type Config struct { Child *Child }
type Child struct { Value *int }
func nestedValue(config *Config) int {
if config == nil {
return 0
}
return *(*config).Child.Value
}import { Rule, ai, scopes } from '@alex-dbs/coderuler';
export default class GoDereferenceGuard extends Rule {
enabled = false;
files = ['**/*.go'];
scope = scopes.astGrep({
language: 'go',
rule: {
any: [{ kind: 'function_declaration' }, { kind: 'method_declaration' }],
has: {
kind: 'unary_expression',
stopBy: 'end',
has: {
field: 'operator',
regex: '^\\*$',
},
},
},
});
check = ai({
instruction: `Check each Go pointer for nil before dereferencing it. For nested fields, check every pointer in the field path.`,
violationMessage: 'Check each pointer for nil before using *.',
});
}Read the full source policy This rule selects HTTP option values in internal Protobuf files. Each URL must use the /service/vN/ prefix. Optional subpaths are allowed.
The selected URL, /v1/invoices, is missing /service.
tests/manual/proto/internal/service.proto:9
syntax = "proto3";
import "google/api/annotations.proto";
package acme.billing.v1;
message Request {}
message Response {}
service BillingService {
rpc ListInvoices(Request) returns (Response) {
option (google.api.http) = {
get: "/v1/invoices"
};
}
}import { Rule, regex, scopes, test } from '@alex-dbs/coderuler';
export default class ServiceProto extends Rule {
enabled = false;
files = ['tests/manual/proto/internal/*.proto'];
scope = scopes.protobuf({ path: "$.rpcs[*].options['google.api.http']..['get','put','post','delete','patch','path']" });
check = regex({
pattern: /^\/service\/v\d+\/[a-z][a-z0-9_]*s(?:\/.*)?$/,
violationMessage: 'Service endpoints must start with a /service/vX/ .',
});
}Read the full source policy This rule uses plain code and the Ajv dependency to check each config file against a JSON Schema.
The value "8080" is a string, so it does not match the schema's integer requirement.
config/app.json:2
{
"port": "8080"
}import Ajv from 'ajv';
import { Rule, pass, scopes, test, violation } from '@alex-dbs/coderuler';
import type { Evaluator, ScopeSubject } from '@alex-dbs/coderuler';
const validateConfig = new Ajv().compile({
type: 'object',
properties: { port: { type: 'integer', minimum: 1, maximum: 65535 } },
required: ['port'],
});
export default class JsonSchemaConfig extends Rule {
enabled = false;
files = ['config/**/*.json'];
scope = scopes.json();
check: Evaluator = subject => {
const { data } = subject as ScopeSubject;
if (validateConfig(data)) return pass();
const error = validateConfig.errors?.[0];
const path = error?.instancePath || '/';
const message = error?.message ?? 'invalid value';
return violation(`${path}: ${message}`);
};
}Read the full source policy Get started
Give developers quick feedback on code issues before review. Let AI focus on the harder questions instead of checking every convention.