@aurelienbbn/agentlint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +1355 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.d.mts +265 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +98 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node-yh9mLvnE.mjs +137 -0
- package/dist/node-yh9mLvnE.mjs.map +1 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- package/package.json +69 -0
- package/skills/agentlint/rule-advisor/SKILL.md +131 -0
- package/skills/agentlint/usage/SKILL.md +157 -0
package/dist/bin.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":["#filename","#source","Parser","TSParser","Parser","Parser"],"sources":["../src/config/env.ts","../src/shared/infrastructure/config-loader.ts","../src/shared/infrastructure/state-store.ts","../src/domain/hash.ts","../src/domain/rule-context.ts","../src/shared/pipeline/file-resolver.ts","../src/shared/infrastructure/git.ts","../src/shared/infrastructure/ignore-reader.ts","../src/shared/infrastructure/parser.ts","../src/shared/pipeline/tree-walker.ts","../src/shared/pipeline/language-map.ts","../src/shared/pipeline/collect-flags.ts","../src/features/check/request.ts","../src/features/check/handler.ts","../src/features/init/request.ts","../src/features/init/handler.ts","../src/features/list/request.ts","../src/features/list/handler.ts","../src/features/review/request.ts","../src/features/review/handler.ts","../src/cli/reporter.ts","../src/bin.ts"],"sourcesContent":["/**\n * Centralised process / environment access.\n *\n * **This is the only module in the codebase that may touch `process.*`.**\n *\n * Every other module that needs the working directory, TTY state, colour\n * preference, or exit-code control must depend on the `Env` service\n * instead of reaching into `process` directly.\n *\n * The layer is built once at startup with `Layer.sync` (no external\n * dependencies), so it can be provided before every other service layer.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Layer } from \"effect\";\nimport * as ServiceMap from \"effect/ServiceMap\";\n\n/**\n * Read-only snapshot of the runtime environment.\n *\n * @since 0.1.0\n * @category services\n */\nexport class Env extends ServiceMap.Service<\n Env,\n {\n /** Current working directory, captured at startup. */\n readonly cwd: string;\n /** `true` when ANSI colour codes should be suppressed (`NO_COLOR` or non-TTY). */\n readonly noColor: boolean;\n /** `true` when stdout is an interactive terminal. */\n readonly isTTY: boolean;\n /** Set the process exit code (non-zero signals failure to the shell). */\n setExitCode(code: number): void;\n }\n>()(\"agentreview/Env\") {\n /**\n * Default layer — reads from `process` globals exactly once.\n *\n * @since 0.1.0\n * @category layers\n */\n static readonly layer: Layer.Layer<Env> = Layer.sync(Env, () => {\n /* eslint-disable n/no-process-env -- single authorised access point */\n const isTTY = process.stdout.isTTY ?? false;\n return Env.of({\n cwd: process.cwd(),\n noColor: !!process.env[\"NO_COLOR\"] || !isTTY,\n isTTY,\n setExitCode: (code) => {\n process.exitCode = code;\n },\n });\n /* eslint-enable n/no-process-env */\n });\n}\n","/**\n * Configuration file discovery and loading.\n *\n * Searches the current working directory for a config file, imports it\n * via `jiti` (for TypeScript support without pre-compilation), and\n * validates the exported shape.\n *\n * **Search order**: `agentreview.config.ts` → `.js` → `.mts` → `.mjs`.\n * The first match wins.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, FileSystem, Layer, Path, Schema } from \"effect\";\nimport * as ServiceMap from \"effect/ServiceMap\";\nimport { Env } from \"../../config/env.js\";\nimport type { AgentReviewConfig } from \"../../domain/config.js\";\n\n/**\n * Raised when the config file is missing, malformed, or fails to import.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class ConfigError extends Schema.TaggedErrorClass<ConfigError>()(\"ConfigError\", {\n message: Schema.String,\n}) {}\n\n/**\n * Candidate config file names, checked in order.\n *\n * @since 0.1.0\n * @category constants\n */\nconst CONFIG_NAMES = [\n \"agentreview.config.ts\",\n \"agentreview.config.js\",\n \"agentreview.config.mts\",\n \"agentreview.config.mjs\",\n];\n\n/**\n * Discover the config file path by checking candidates in order.\n *\n * @since 0.1.0\n * @category internals\n */\nconst discoverConfig = (fs: FileSystem.FileSystem, path: Path.Path, cwd: string): Effect.Effect<string, ConfigError> =>\n Effect.gen(function* () {\n for (const name of CONFIG_NAMES) {\n const candidate = path.resolve(cwd, name);\n if (yield* fs.exists(candidate).pipe(Effect.orElseSucceed(() => false))) {\n return candidate;\n }\n }\n return yield* new ConfigError({\n message: `No agentreview config found. Create agentreview.config.ts in ${cwd}`,\n });\n });\n\n/**\n * Effect service that discovers and loads the agentreview config file.\n *\n * Uses `jiti` under the hood so TypeScript configs work without a\n * separate compilation step.\n *\n * @example\n * ```ts\n * import { Console, Effect } from \"effect\"\n * import { ConfigLoader } from \"./infrastructure/config-loader.js\"\n *\n * const program = Effect.gen(function* () {\n * const loader = yield* ConfigLoader\n * const config = yield* loader.load()\n * yield* Console.log(Object.keys(config.rules))\n * })\n * ```\n *\n * @since 0.1.0\n * @category services\n */\nexport class ConfigLoader extends ServiceMap.Service<\n ConfigLoader,\n {\n /** Discover and import the config file from the working directory. */\n load(): Effect.Effect<AgentReviewConfig, ConfigError>;\n }\n>()(\"agentreview/ConfigLoader\") {\n static readonly layer: Layer.Layer<ConfigLoader, never, FileSystem.FileSystem | Path.Path | Env> = Layer.effect(\n ConfigLoader,\n Effect.gen(function* () {\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n return ConfigLoader.of({\n load: () =>\n Effect.gen(function* () {\n const configPath = yield* discoverConfig(fs, path, env.cwd);\n\n const config = yield* Effect.tryPromise({\n try: async () => {\n const { createJiti } = await import(\"jiti\");\n const jiti = createJiti(import.meta.url, {\n interopDefault: true,\n });\n const loaded = await jiti.import(configPath);\n return (loaded as { default?: AgentReviewConfig }).default ?? (loaded as AgentReviewConfig);\n },\n catch: (error) =>\n new ConfigError({\n message: error instanceof Error ? error.message : String(error),\n }),\n });\n\n if (!config || typeof config !== \"object\" || !(\"rules\" in config)) {\n return yield* new ConfigError({\n message: `Invalid config at ${configPath}: must export an object with a \"rules\" field`,\n });\n }\n\n return config;\n }),\n });\n }),\n );\n}\n","/**\n * Local state store for tracking reviewed flags.\n *\n * Manages a `.agentreview-state` file in the project root that stores\n * hashes of flags that have been reviewed. This file is intended to\n * be **gitignored** — it is per-developer scratch state for tracking\n * progress during review sweeps.\n *\n * **Caveats**:\n * - Hashes encode file path, line, column, and message. Editing code\n * above a reviewed flag shifts its position and invalidates the hash.\n * This is by design — changed context should be re-reviewed.\n * - Stale hashes (from flags that no longer exist) accumulate harmlessly.\n * Use `agentreview review --reset` to start fresh.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, FileSystem, HashSet, Layer, Path } from \"effect\";\nimport * as ServiceMap from \"effect/ServiceMap\";\nimport { Env } from \"../../config/env.js\";\n\n/**\n * The filename used for local review state.\n *\n * @since 0.1.0\n * @category constants\n */\nconst STATE_FILENAME = \".agentreview-state\";\n\n/**\n * Parse the state file into a set of hashes.\n * Tolerates blank lines and `#`-prefixed comments.\n *\n * @since 0.1.0\n * @category internals\n */\nfunction parseStateFile(content: string): HashSet.HashSet<string> {\n let hashes: HashSet.HashSet<string> = HashSet.empty();\n for (const raw of content.split(\"\\n\")) {\n const line = raw.trim();\n if (line.length > 0 && !line.startsWith(\"#\")) {\n hashes = HashSet.add(hashes, line);\n }\n }\n return hashes;\n}\n\n/**\n * Serialize a set of hashes into file content.\n *\n * @since 0.1.0\n * @category internals\n */\nfunction serializeHashes(hashes: HashSet.HashSet<string>): string {\n return [...hashes].join(\"\\n\") + \"\\n\";\n}\n\n/**\n * Effect service for loading and persisting reviewed-flag state.\n *\n * @since 0.1.0\n * @category services\n */\nexport class StateStore extends ServiceMap.Service<\n StateStore,\n {\n /** Load reviewed hashes from `.agentreview-state`. Returns an empty set if the file is missing. */\n load(): Effect.Effect<HashSet.HashSet<string>>;\n /** Append one or more hashes to `.agentreview-state`, deduplicating against existing entries. */\n append(hashes: ReadonlyArray<string>): Effect.Effect<void>;\n /** Delete the `.agentreview-state` file entirely. */\n reset(): Effect.Effect<void>;\n }\n>()(\"agentreview/StateStore\") {\n /**\n * Default layer — resolves the state file path from `Env.cwd`.\n *\n * @since 0.1.0\n * @category layers\n */\n static readonly layer: Layer.Layer<StateStore, never, FileSystem.FileSystem | Path.Path | Env> = Layer.unwrap(\n Effect.gen(function* () {\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const statePath = path.resolve(env.cwd, STATE_FILENAME);\n\n return Layer.succeed(\n StateStore,\n StateStore.of({\n load: () =>\n fs.exists(statePath).pipe(\n Effect.orElseSucceed(() => false),\n Effect.flatMap((exists) =>\n exists\n ? fs.readFileString(statePath).pipe(\n Effect.map(parseStateFile),\n Effect.orElseSucceed(() => HashSet.empty<string>()),\n )\n : Effect.succeed(HashSet.empty<string>()),\n ),\n ),\n\n append: (hashes) =>\n fs.exists(statePath).pipe(\n Effect.orElseSucceed(() => false),\n Effect.flatMap((exists) =>\n exists\n ? fs.readFileString(statePath).pipe(\n Effect.map(parseStateFile),\n Effect.orElseSucceed(() => HashSet.empty<string>()),\n )\n : Effect.succeed(HashSet.empty<string>()),\n ),\n Effect.map((existing) => hashes.reduce((acc, h) => HashSet.add(acc, h), existing)),\n Effect.flatMap((merged) =>\n fs.writeFileString(statePath, serializeHashes(merged)).pipe(Effect.orElseSucceed(() => {})),\n ),\n ),\n\n reset: () =>\n fs.exists(statePath).pipe(\n Effect.orElseSucceed(() => false),\n Effect.flatMap((exists) =>\n exists ? fs.remove(statePath).pipe(Effect.orElseSucceed(() => {})) : Effect.void,\n ),\n ),\n }),\n );\n }),\n );\n}\n","/**\n * FNV-1a hashing utility.\n *\n * Produces a 7-character hex digest used for stable, deterministic\n * flag identification. The hash encodes rule name, file path, position,\n * and message so that identical matches across runs share the same id.\n *\n * @see https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function\n *\n * @module\n * @since 0.1.0\n */\n\n/**\n * FNV-1a 32-bit offset basis.\n *\n * @since 0.1.0\n * @category constants\n */\nconst FNV_OFFSET_BASIS = 0x811c9dc5;\n\n/**\n * FNV-1a 32-bit prime multiplier.\n *\n * @since 0.1.0\n * @category constants\n */\nconst FNV_PRIME = 0x01000193;\n\n/**\n * Compute a 7-character hex FNV-1a hash of `input`.\n *\n * The result is the first 7 hex characters of the unsigned 32-bit\n * FNV-1a digest — short enough for display, long enough to avoid\n * collisions in typical lint runs.\n *\n * @example\n * ```ts\n * import { fnv1a7 } from \"./utils/hash.js\"\n *\n * fnv1a7(\"my-rule:src/index.ts:10:1:message\") // => \"a3f4b2c\"\n * ```\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function fnv1a7(input: string): string {\n let hash = FNV_OFFSET_BASIS;\n for (let i = 0; i < input.length; i++) {\n hash ^= input.charCodeAt(i);\n hash = Math.imul(hash, FNV_PRIME);\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\").slice(0, 7);\n}\n","/**\n * Rule context — the interface rules use to interact with the runner.\n *\n * Provides file metadata, source access, and the {@link RuleContext.flag}\n * method for recording matches.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { fnv1a7 } from \"./hash.js\";\nimport { type FlagOptions, FlagRecord } from \"./flag.js\";\n\n/**\n * Context object passed to `createOnce`. Available throughout the rule's lifecycle.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface RuleContext {\n /** Absolute path of the current file being analyzed. */\n getFilename(): string;\n /** Full source content of the current file. */\n getSourceCode(): string;\n /**\n * Lines around the given 1-based line number, formatted with line numbers.\n * @param line 1-based line number\n * @param radius number of lines above/below to include (default 10)\n */\n getLinesAround(line: number, radius?: number): string;\n /** Record a match for the output report. */\n flag(options: FlagOptions): void;\n}\n\n/**\n * Internal implementation of {@link RuleContext}.\n *\n * Tracks the current file, accumulates flags, and provides source\n * access helpers. The check command calls {@link setFile} before each\n * file and {@link drainFlags} after the tree walk to collect results.\n *\n * @since 0.1.0\n * @category internals\n */\nexport class RuleContextImpl implements RuleContext {\n readonly ruleName: string;\n readonly flags: FlagRecord[] = [];\n\n #filename = \"\";\n #source = \"\";\n\n constructor(ruleName: string) {\n this.ruleName = ruleName;\n }\n\n /**\n * Set the current file context. Called by the check command before\n * each file is walked.\n */\n setFile(filename: string, source: string): void {\n this.#filename = filename;\n this.#source = source;\n }\n\n /**\n * Remove and return all accumulated flags. Called after the tree\n * walk for each file to collect results.\n */\n drainFlags(): FlagRecord[] {\n return this.flags.splice(0);\n }\n\n getFilename(): string {\n return this.#filename;\n }\n\n getSourceCode(): string {\n return this.#source;\n }\n\n getLinesAround(line: number, radius = 10): string {\n const lines = this.#source.split(\"\\n\");\n const start = Math.max(0, line - 1 - radius);\n const end = Math.min(lines.length, line + radius);\n return lines\n .slice(start, end)\n .map((l, i) => `${String(start + i + 1).padStart(4)} | ${l}`)\n .join(\"\\n\");\n }\n\n flag(options: FlagOptions): void {\n const line = options.node.startPosition.row + 1;\n const col = options.node.startPosition.column + 1;\n const sourceLines = this.#source.split(\"\\n\");\n const rawLine = sourceLines[line - 1] ?? \"\";\n const trimmed = rawLine.trim();\n const sourceSnippet = trimmed.length > 100 ? trimmed.slice(0, 97) + \"...\" : trimmed;\n\n const hash = fnv1a7(`${this.ruleName}:${this.#filename}:${line}:${col}:${options.message}`);\n\n this.flags.push(\n new FlagRecord({\n ruleName: this.ruleName,\n filename: this.#filename,\n line,\n col,\n message: options.message,\n sourceSnippet,\n hash,\n instruction: options.instruction,\n suggest: options.suggest,\n }),\n );\n }\n}\n","/**\n * File resolution service.\n *\n * Determines which files to lint by applying the 6-layer filter pipeline:\n * 1. Candidate files (from git diff or all files)\n * 2. Built-in ignores\n * 3. .gitignore\n * 4. .agentreviewignore\n * 5. Config include/ignore\n * (Steps 2-4 are handled by IgnoreReader)\n *\n * Per-rule filtering (languages, include, ignore) is done by the check command.\n *\n * @module\n */\n\nimport { Effect, FileSystem, HashSet, Path, Schema } from \"effect\";\nimport { Env } from \"../../config/env.js\";\nimport picomatch from \"picomatch\";\n\n/**\n * Raised when file resolution fails — e.g. a git error bubbling up\n * from the changed-files query.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class FileResolverError extends Schema.TaggedErrorClass<FileResolverError>()(\"FileResolverError\", {\n message: Schema.String,\n}) {}\n\n/**\n * Options controlling which files enter the lint pipeline.\n *\n * @since 0.1.0\n * @category models\n */\nexport const ResolveOptions = Schema.Struct({\n /** When `true`, scan all files instead of only git-changed files. */\n all: Schema.Boolean,\n /** Git ref to diff against. Defaults to the detected default branch. */\n baseRef: Schema.optional(Schema.String),\n /** Global include globs from the config file. */\n configInclude: Schema.optional(Schema.Array(Schema.String)),\n /** Global ignore globs from the config file. */\n configIgnore: Schema.optional(Schema.Array(Schema.String)),\n /** Explicit file paths passed as CLI positional arguments. */\n positionalFiles: Schema.optional(Schema.Array(Schema.String)),\n});\n\n/** @since 0.1.0 */\nexport type ResolveOptions = Schema.Schema.Type<typeof ResolveOptions>;\n\n/** Directories that are always skipped during recursive listing. */\nconst SKIP_DIRS: HashSet.HashSet<string> = HashSet.make(\"node_modules\", \".git\", \"dist\");\n\n/**\n * Recursively list all files under `dir`, returning paths relative to `base`.\n *\n * Skips `node_modules`, `.git`, and `dist` directories. Errors (e.g.\n * permission denied) are silently swallowed.\n *\n * Uses the Effect `FileSystem` and `Path` services for cross-platform\n * file system access.\n *\n * @since 0.1.0\n * @category internals\n */\nfunction listAllFiles(dir: string, base: string, fs: FileSystem.FileSystem, path: Path.Path): Effect.Effect<string[]> {\n return Effect.gen(function* () {\n const entries = yield* fs.readDirectory(dir);\n const results: string[] = [];\n\n for (const name of entries) {\n if (HashSet.has(SKIP_DIRS, name)) continue;\n\n const fullPath = path.resolve(dir, name);\n const info = yield* fs.stat(fullPath);\n const relPath = path.relative(base, fullPath).replace(/\\\\/g, \"/\");\n\n if (info.type === \"Directory\") {\n results.push(...(yield* listAllFiles(fullPath, base, fs, path)));\n } else {\n results.push(relPath);\n }\n }\n\n return results;\n }).pipe(Effect.catch(() => Effect.succeed([] as string[])));\n}\n\n/**\n * Determine the final set of files to lint.\n *\n * Applies the multi-layer filter pipeline described in the module header,\n * then sorts the result alphabetically for deterministic output.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function resolveFiles(\n options: ResolveOptions,\n ignoreReader: { isIgnored(path: string): boolean },\n gitService: {\n changedFiles(baseRef?: string): Effect.Effect<ReadonlyArray<string>, any>;\n },\n): Effect.Effect<ReadonlyArray<string>, FileResolverError, FileSystem.FileSystem | Path.Path | Env> {\n return Effect.gen(function* () {\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const { cwd } = env;\n let candidates: string[];\n\n if (options.positionalFiles && options.positionalFiles.length > 0) {\n candidates = [...options.positionalFiles];\n } else if (options.all) {\n candidates = yield* listAllFiles(cwd, cwd, fs, path);\n } else {\n const changed = yield* Effect.mapError(\n gitService.changedFiles(options.baseRef),\n (e) => new FileResolverError({ message: `Git error: ${e}` }),\n );\n candidates = [...changed];\n }\n\n const includeMatcher = options.configInclude?.length ? picomatch(options.configInclude as string[]) : undefined;\n const ignoreMatcher = options.configIgnore?.length ? picomatch(options.configIgnore as string[]) : undefined;\n\n return candidates\n .filter((f) => !ignoreReader.isIgnored(f))\n .filter((f) => !includeMatcher || includeMatcher(f))\n .filter((f) => !ignoreMatcher || !ignoreMatcher(f))\n .filter((f) => path.extname(f).length > 0)\n .toSorted();\n });\n}\n","/**\n * Git integration — default branch detection and changed file collection.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, HashSet, Layer, Schema } from \"effect\";\nimport * as ServiceMap from \"effect/ServiceMap\";\nimport { Env } from \"../../config/env.js\";\nimport { ChildProcess, ChildProcessSpawner } from \"effect/unstable/process\";\n\n/**\n * Raised when a git operation fails — e.g. not a git repo,\n * invalid ref, or `git` binary not found.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class GitError extends Schema.TaggedErrorClass<GitError>()(\"GitError\", {\n message: Schema.String,\n}) {}\n\n/**\n * Execute a git command and return trimmed stdout.\n *\n * Uses the array form of `ChildProcess.make` so that dynamic arguments\n * are properly tokenized.\n *\n * @since 0.1.0\n * @category internals\n */\nconst gitCmd = (args: string, cwd: string) =>\n Effect.gen(function* () {\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n return (yield* spawner.string(ChildProcess.make(\"git\", args.split(/\\s+/), { cwd }))).trim();\n });\n\n/**\n * Detect the default branch by checking whether `main` or `master` exists.\n * Falls back to `\"main\"` when neither can be verified.\n *\n * @since 0.1.0\n * @category internals\n */\nconst detectDefault = (cwd: string) =>\n gitCmd(\"rev-parse --verify main\", cwd).pipe(\n Effect.map(() => \"main\" as string),\n Effect.catch(() =>\n gitCmd(\"rev-parse --verify master\", cwd).pipe(\n Effect.map(() => \"master\" as string),\n Effect.catch(() => Effect.succeed(\"main\" as string)),\n ),\n ),\n );\n\n/**\n * Collect all files that differ from `baseRef`.\n *\n * Gathers the union of committed diffs, uncommitted changes, and\n * untracked files. Each source is caught so partial failures\n * (e.g. empty repo, no merge-base) are silently skipped.\n *\n * @since 0.1.0\n * @category internals\n */\nconst parseLines = (output: string): ReadonlyArray<string> =>\n output\n .split(\"\\n\")\n .map((f) => f.trim())\n .filter((f) => f.length > 0);\n\nconst collectChangedFiles = (cwd: string, baseRef: string) =>\n Effect.all([\n // Committed changes since merge-base\n gitCmd(`merge-base HEAD ${baseRef}`, cwd).pipe(\n Effect.flatMap((mergeBase) => gitCmd(`diff --name-only ${mergeBase}...HEAD`, cwd)),\n Effect.catch(() => Effect.succeed(\"\")),\n ),\n // Uncommitted changes\n gitCmd(\"diff --name-only HEAD\", cwd).pipe(Effect.catch(() => Effect.succeed(\"\"))),\n // Untracked files\n gitCmd(\"ls-files --others --exclude-standard\", cwd).pipe(Effect.catch(() => Effect.succeed(\"\"))),\n ]).pipe(\n Effect.map(([committed, uncommitted, untracked]) =>\n [\n ...HashSet.fromIterable([...parseLines(committed), ...parseLines(uncommitted), ...parseLines(untracked)]),\n ].toSorted(),\n ),\n );\n\n/**\n * @example\n * ```ts\n * import { Console, Effect } from \"effect\"\n * import { Git } from \"./infrastructure/git.js\"\n *\n * const program = Effect.gen(function* () {\n * const git = yield* Git\n * const branch = yield* git.detectDefaultBranch()\n * const changed = yield* git.changedFiles(branch)\n * yield* Console.log(`${changed.length} files changed since ${branch}`)\n * })\n * ```\n *\n * @since 0.1.0\n */\nexport class Git extends ServiceMap.Service<\n Git,\n {\n /** Detect whether the default branch is `main` or `master`. */\n detectDefaultBranch(): Effect.Effect<string, GitError>;\n /** Return sorted list of files changed relative to `baseRef` (defaults to the detected default branch). */\n changedFiles(baseRef?: string): Effect.Effect<ReadonlyArray<string>, GitError>;\n }\n>()(\"agentreview/Git\") {\n static readonly layer: Layer.Layer<Git, never, ChildProcessSpawner.ChildProcessSpawner | Env> = Layer.effect(\n Git,\n Effect.gen(function* () {\n const env = yield* Env;\n const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;\n const provide = <A, E>(effect: Effect.Effect<A, E, ChildProcessSpawner.ChildProcessSpawner>) =>\n Effect.provideService(effect, ChildProcessSpawner.ChildProcessSpawner, spawner);\n\n return Git.of({\n detectDefaultBranch: () =>\n provide(detectDefault(env.cwd)).pipe(Effect.mapError((e) => new GitError({ message: String(e) }))),\n\n changedFiles: (baseRef) =>\n (baseRef ? Effect.succeed(baseRef) : provide(detectDefault(env.cwd))).pipe(\n Effect.flatMap((base) => provide(collectChangedFiles(env.cwd, base))),\n Effect.mapError((e) => new GitError({ message: String(e) })),\n ),\n });\n }),\n );\n}\n","/**\n * Ignore-pattern aggregation service.\n *\n * Merges built-in ignore patterns (e.g. `node_modules`, `dist`) with\n * the project's `.gitignore` to produce a single `isIgnored` predicate.\n * Patterns are compiled once via `picomatch` and reused for every file.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, FileSystem, Layer, Path } from \"effect\";\nimport * as ServiceMap from \"effect/ServiceMap\";\nimport { Env } from \"../../config/env.js\";\nimport picomatch from \"picomatch\";\n\n/**\n * Paths that are always ignored regardless of user configuration.\n *\n * Covers common build output, package manager artifacts, source maps,\n * lockfiles, and cache directories.\n *\n * @since 0.1.0\n * @category constants\n */\nconst BUILTIN_IGNORE_PATTERNS = [\n \"node_modules/**\",\n \"dist/**\",\n \"build/**\",\n \".git/**\",\n \".next/**\",\n \".cache/**\",\n \"coverage/**\",\n \"**/*.min.js\",\n \"**/*.min.css\",\n \"**/*.map\",\n \"**/*.lock\",\n \"**/*.log\",\n \"**/*.tsbuildinfo\",\n \".agentreview-state\",\n];\n\n/**\n * Parse a `.gitignore`-style file into an array of glob patterns.\n * Strips blank lines and comments (lines starting with `#`).\n *\n * @since 0.1.0\n * @category internals\n */\nfunction parseIgnoreFile(content: string): ReadonlyArray<string> {\n return content\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"));\n}\n\n/**\n * Effect service that tests whether a file path should be ignored.\n *\n * Combines built-in patterns with `.gitignore` into a single\n * `picomatch` matcher. Constructed once per run.\n *\n * @since 0.1.0\n * @category services\n */\nexport class IgnoreReader extends ServiceMap.Service<\n IgnoreReader,\n {\n /** Returns `true` if `filepath` matches any built-in or `.gitignore` pattern. */\n isIgnored(filepath: string): boolean;\n }\n>()(\"agentreview/IgnoreReader\") {\n static readonly layer: Layer.Layer<IgnoreReader, never, FileSystem.FileSystem | Path.Path | Env> = Layer.unwrap(\n Effect.gen(function* () {\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const { cwd } = env;\n const gitignorePath = path.resolve(cwd, \".gitignore\");\n\n const gitignoreExists = yield* fs.exists(gitignorePath).pipe(Effect.orElseSucceed(() => false));\n const gitignorePatterns: ReadonlyArray<string> = gitignoreExists\n ? parseIgnoreFile(yield* fs.readFileString(gitignorePath).pipe(Effect.orElseSucceed(() => \"\")))\n : [];\n\n const allPatterns = [...BUILTIN_IGNORE_PATTERNS, ...gitignorePatterns];\n const matcher = picomatch(allPatterns, { dot: true });\n\n return Layer.succeed(\n IgnoreReader,\n IgnoreReader.of({\n isIgnored: (filepath) => matcher(filepath),\n }),\n );\n }),\n );\n}\n","/**\n * Tree-sitter WASM parser.\n *\n * WASM init is lazy — the first `parse` call triggers initialization.\n * Grammars are cached after first load.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, FileSystem, HashMap, Layer, Option, Path, Schema } from \"effect\";\nimport * as ServiceMap from \"effect/ServiceMap\";\nimport { Env } from \"../../config/env.js\";\nimport { Language, Parser as TSParser, type Tree } from \"web-tree-sitter\";\n\n/**\n * Raised when parsing fails — e.g. missing grammar, corrupt WASM, or\n * tree-sitter returning a null tree.\n *\n * @since 0.1.0\n * @category errors\n */\nexport class ParserError extends Schema.TaggedErrorClass<ParserError>()(\"ParserError\", {\n message: Schema.String,\n}) {}\n\n/**\n * Maps grammar names to their corresponding `.wasm` filenames.\n *\n * @since 0.1.0\n * @category constants\n */\nconst GRAMMAR_FILES: HashMap.HashMap<string, string> = HashMap.make(\n [\"typescript\", \"tree-sitter-typescript.wasm\"],\n [\"tsx\", \"tree-sitter-tsx.wasm\"],\n [\"javascript\", \"tree-sitter-javascript.wasm\"],\n);\n\n/**\n * @example\n * ```ts\n * import { Console, Effect } from \"effect\"\n * import { Parser } from \"./infrastructure/parser.js\"\n *\n * const program = Effect.gen(function* () {\n * const parser = yield* Parser\n * const tree = yield* parser.parse(\"const x = 1\", \"typescript\")\n * yield* Console.log(tree.rootNode.type) // \"program\"\n * })\n * ```\n *\n * @since 0.1.0\n * @category services\n */\nexport class Parser extends ServiceMap.Service<\n Parser,\n {\n parse(source: string, grammar: string): Effect.Effect<Tree, ParserError>;\n }\n>()(\"agentreview/Parser\") {\n /** Default layer — lazily initializes WASM and caches grammars. */\n static readonly layer: Layer.Layer<Parser, never, FileSystem.FileSystem | Path.Path | Env> = Layer.effect(\n Parser,\n Effect.gen(function* () {\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n const resolveWasmPath = (filename: string): Effect.Effect<string, ParserError> =>\n Effect.gen(function* () {\n const thisDir = path.dirname(path.resolve(import.meta.dirname ?? \".\", \"\"));\n const distPath = path.resolve(thisDir, \"wasm\", filename);\n if (yield* fs.exists(distPath).pipe(Effect.orElseSucceed(() => false))) return distPath;\n\n const nmBase = path.resolve(env.cwd, \"node_modules\");\n if (filename === \"tree-sitter.wasm\") {\n const p = path.resolve(nmBase, \"web-tree-sitter\", filename);\n if (yield* fs.exists(p).pipe(Effect.orElseSucceed(() => false))) return p;\n } else {\n const p = path.resolve(nmBase, \"tree-sitter-wasms\", \"out\", filename);\n if (yield* fs.exists(p).pipe(Effect.orElseSucceed(() => false))) return p;\n }\n\n return yield* new ParserError({ message: `WASM file not found: ${filename}` });\n });\n\n let parserInstance: TSParser | undefined;\n let languageCache: HashMap.HashMap<string, Language> = HashMap.empty();\n\n return Parser.of({\n parse: (source, grammar) =>\n Effect.gen(function* () {\n if (!parserInstance) {\n const initPath = yield* resolveWasmPath(\"tree-sitter.wasm\");\n yield* Effect.tryPromise({\n try: async () => {\n await TSParser.init({ locateFile: () => initPath });\n parserInstance = new TSParser();\n },\n catch: (error) => new ParserError({ message: error instanceof Error ? error.message : String(error) }),\n });\n }\n\n let lang = Option.getOrUndefined(HashMap.get(languageCache, grammar));\n if (!lang) {\n const file = Option.getOrUndefined(HashMap.get(GRAMMAR_FILES, grammar));\n if (!file) return yield* new ParserError({ message: `Unknown grammar: ${grammar}` });\n\n const wasmPath = yield* resolveWasmPath(file);\n lang = yield* Effect.tryPromise({\n try: () => Language.load(wasmPath),\n catch: (error) => new ParserError({ message: error instanceof Error ? error.message : String(error) }),\n });\n languageCache = HashMap.set(languageCache, grammar, lang);\n }\n\n parserInstance!.setLanguage(lang);\n const tree = parserInstance!.parse(source);\n if (!tree) return yield* new ParserError({ message: \"Parser returned null tree\" });\n return tree;\n }),\n });\n }),\n );\n}\n","/**\n * Single-pass multi-rule tree walker.\n *\n * Builds a dispatch table from all active rules' visitor methods,\n * walks the tree once using tree-sitter's cursor API, and calls\n * all matching handlers per node.\n *\n * @module\n */\n\nimport { Effect, HashMap, Option } from \"effect\";\nimport type { Tree, TreeCursor } from \"web-tree-sitter\";\nimport { type AgentReviewNode, wrapNode } from \"../../domain/node.js\";\nimport type { FlagRecord } from \"../../domain/flag.js\";\nimport type { VisitorHandler, Visitors } from \"../../domain/rule.js\";\nimport type { RuleContextImpl } from \"../../domain/rule-context.js\";\n\n/**\n * Regex matching `agentreview-ignore` comments.\n *\n * Captures an optional rule name after the directive:\n * - `// agentreview-ignore` → suppresses all rules on the next line\n * - `// agentreview-ignore my-rule` → suppresses only `my-rule`\n *\n * @since 0.1.0\n * @category constants\n */\nconst IGNORE_PATTERN = /agentreview-ignore(?:\\s+(\\S+))?/;\n\n/**\n * Internal binding of a rule to its context and visitors for a walk pass.\n *\n * @since 0.1.0\n * @category models\n */\ninterface RuleEntry {\n readonly ruleName: string;\n readonly context: RuleContextImpl;\n readonly visitors: Visitors;\n}\n\n/**\n * A single handler entry in the dispatch table, keyed by node type.\n *\n * @since 0.1.0\n * @category models\n */\ninterface DispatchHandler {\n readonly ruleName: string;\n readonly handler: VisitorHandler;\n}\n\n/**\n * Records an `agentreview-ignore` comment that suppresses a specific\n * rule (or all rules) on the immediately following line.\n *\n * @since 0.1.0\n * @category models\n */\ninterface Suppression {\n /** Rule name, or `\"*\"` for all rules. */\n readonly ruleName: string;\n /** 0-indexed line number of the *comment* (the suppressed line is `line + 1`). */\n readonly line: number;\n}\n\n/**\n * Walk files with the given rules, collecting all flags.\n *\n * Call this once per file. The caller is responsible for:\n * - Calling `context.setFile()` before this function\n * - Calling `before()` and filtering out skipped rules\n * - Calling `after()` after all files are processed\n *\n * @internal\n */\nexport function walkFile(tree: Tree, rules: ReadonlyArray<RuleEntry>): ReadonlyArray<FlagRecord> {\n const dispatchTable: HashMap.HashMap<string, DispatchHandler[]> = HashMap.mutate(\n HashMap.empty<string, DispatchHandler[]>(),\n (m) => {\n for (const entry of rules) {\n for (const key of Object.keys(entry.visitors)) {\n if (key === \"before\" || key === \"after\") continue;\n const handler = entry.visitors[key];\n if (typeof handler !== \"function\") continue;\n\n const existing = Option.getOrUndefined(HashMap.get(m, key));\n if (existing) {\n existing.push({ ruleName: entry.ruleName, handler: handler as VisitorHandler });\n } else {\n HashMap.set(m, key, [{ ruleName: entry.ruleName, handler: handler as VisitorHandler }]);\n }\n }\n }\n },\n );\n\n const suppressions: Suppression[] = [];\n\n const cursor: TreeCursor = tree.walk();\n let reachedEnd = false;\n\n while (!reachedEnd) {\n const nodeType = cursor.nodeType;\n\n if (nodeType === \"comment\") {\n const node = cursor.currentNode;\n const match = IGNORE_PATTERN.exec(node.text);\n if (match) {\n const ruleNameArg = match[1];\n const ruleName = ruleNameArg?.split(\"--\")[0]?.trim() ?? \"*\";\n suppressions.push({\n ruleName,\n line: node.startPosition.row + 1,\n });\n }\n }\n\n const handlers = Option.getOrUndefined(HashMap.get(dispatchTable, nodeType));\n if (handlers) {\n const wrapped: AgentReviewNode = wrapNode(cursor.currentNode);\n for (const { handler } of handlers) {\n handler(wrapped);\n }\n }\n\n if (cursor.gotoFirstChild()) continue;\n while (!cursor.gotoNextSibling()) {\n if (!cursor.gotoParent()) {\n reachedEnd = true;\n break;\n }\n }\n }\n\n const allFlags: FlagRecord[] = [];\n for (const entry of rules) {\n allFlags.push(...entry.context.drainFlags());\n }\n\n if (suppressions.length === 0) return allFlags;\n\n return allFlags.filter((flag) => {\n const flagLine0 = flag.line - 1;\n return !suppressions.some((s) => (s.ruleName === \"*\" || s.ruleName === flag.ruleName) && s.line === flagLine0);\n });\n}\n\n/**\n * Effect wrapper around {@link walkFile}.\n *\n * Runs the tree walk synchronously inside `Effect.sync`, making it\n * composable with the rest of the Effect pipeline.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function walkFileEffect(tree: Tree, rules: ReadonlyArray<RuleEntry>): Effect.Effect<ReadonlyArray<FlagRecord>> {\n return Effect.sync(() => walkFile(tree, rules));\n}\n","/**\n * File extension → tree-sitter grammar mapping.\n *\n * Maps every supported file extension to the grammar name used by\n * the parser service. This is the single source of truth for which\n * file types agentreview can analyze.\n *\n * Uses Effect `HashMap` for an immutable, structurally-equal lookup table.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { HashMap, Option } from \"effect\";\n\n/**\n * Maps file extensions (without leading dot) to their tree-sitter\n * grammar name.\n *\n * @since 0.1.0\n * @category constants\n */\nconst EXTENSION_TO_GRAMMAR: HashMap.HashMap<string, string> = HashMap.make(\n [\"ts\", \"typescript\"],\n [\"tsx\", \"tsx\"],\n [\"js\", \"javascript\"],\n [\"jsx\", \"javascript\"],\n [\"mts\", \"typescript\"],\n [\"cts\", \"typescript\"],\n [\"mjs\", \"javascript\"],\n [\"cjs\", \"javascript\"],\n);\n\n/**\n * Look up the tree-sitter grammar name for a file extension.\n *\n * Returns `undefined` for unsupported extensions — callers should\n * skip those files.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function grammarForExtension(ext: string): string | undefined {\n return Option.getOrUndefined(HashMap.get(EXTENSION_TO_GRAMMAR, ext));\n}\n\n/**\n * Return all file extensions that agentreview can parse.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function supportedExtensions(): ReadonlyArray<string> {\n return [...HashMap.keys(EXTENSION_TO_GRAMMAR)];\n}\n","/**\n * Flag collection pipeline — shared between `check` and `review`.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, FileSystem, Path, Schema } from \"effect\";\nimport picomatch from \"picomatch\";\nimport { Env } from \"../../config/env.js\";\nimport { FlagRecord } from \"../../domain/flag.js\";\nimport type { AgentReviewRule, Visitors } from \"../../domain/rule.js\";\nimport { RuleContextImpl } from \"../../domain/rule-context.js\";\nimport { ConfigLoader } from \"../infrastructure/config-loader.js\";\nimport { resolveFiles } from \"./file-resolver.js\";\nimport { Git } from \"../infrastructure/git.js\";\nimport { IgnoreReader } from \"../infrastructure/ignore-reader.js\";\nimport { Parser } from \"../infrastructure/parser.js\";\nimport { walkFile } from \"./tree-walker.js\";\nimport { grammarForExtension } from \"./language-map.js\";\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport const CollectResult = Schema.Struct({\n /** Collected flags. */\n flags: Schema.Array(FlagRecord),\n /** `true` when the `--rule` filter matched no registered rules. */\n noMatchingRules: Schema.Boolean,\n});\n\n/** @since 0.1.0 */\nexport type CollectResult = Schema.Schema.Type<typeof CollectResult>;\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport const CollectOptions = Schema.Struct({\n /** When `true`, scan all files instead of only git-changed files. */\n all: Schema.Boolean,\n /** Rule name filter. Empty array means \"run all rules\". */\n rules: Schema.Array(Schema.String),\n /** When `true`, suppress instruction and hint blocks in output. */\n dryRun: Schema.Boolean,\n /** Git ref to diff against. `undefined` means auto-detect. */\n base: Schema.UndefinedOr(Schema.String),\n /** Explicit file paths from positional CLI arguments. */\n files: Schema.Array(Schema.String),\n});\n\n/** @since 0.1.0 */\nexport type CollectOptions = Schema.Schema.Type<typeof CollectOptions>;\n\n/** @since 0.1.0 */\nexport const collectFlags = Effect.fn(\"collectFlags\")(function* (\n options: CollectOptions,\n): Generator<any, CollectResult> {\n const configLoader = yield* ConfigLoader;\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const ignoreReader = yield* IgnoreReader;\n const gitService = yield* Git;\n const parserService = yield* Parser;\n\n const config = yield* configLoader.load();\n\n let activeRules: Array<[string, AgentReviewRule]> = Object.entries(config.rules);\n if (options.rules.length > 0) {\n activeRules = activeRules.filter(([name]) => options.rules.includes(name));\n if (activeRules.length === 0) return { flags: [], noMatchingRules: true };\n }\n\n const includePatterns = config.include ? [...config.include] : undefined;\n const ignorePatterns = config.ignore ? [...config.ignore] : undefined;\n\n const files = yield* resolveFiles(\n {\n all: options.all,\n baseRef: options.base,\n configInclude: includePatterns,\n configIgnore: ignorePatterns,\n positionalFiles: options.files.length > 0 ? [...options.files] : undefined,\n },\n ignoreReader,\n gitService,\n );\n\n if (files.length === 0) return { flags: [], noMatchingRules: false };\n\n const ruleEntries: Array<{\n name: string;\n rule: AgentReviewRule;\n context: RuleContextImpl;\n visitors: Visitors;\n }> = [];\n\n for (const [name, rule] of activeRules) {\n const context = new RuleContextImpl(name);\n const visitors = rule.createOnce(context);\n ruleEntries.push({ name, rule, context, visitors });\n }\n\n const allFlags: FlagRecord[] = [];\n\n for (const file of files) {\n const ext = path.extname(file).slice(1);\n const absPath = path.resolve(env.cwd, file);\n\n const applicableRules = ruleEntries.filter((entry) => {\n if (!entry.rule.meta.languages.includes(ext)) return false;\n\n if (entry.rule.meta.include && entry.rule.meta.include.length > 0) {\n const matcher = picomatch([...entry.rule.meta.include]);\n if (!matcher(file)) return false;\n }\n\n if (entry.rule.meta.ignore && entry.rule.meta.ignore.length > 0) {\n const matcher = picomatch([...entry.rule.meta.ignore]);\n if (matcher(file)) return false;\n }\n\n return true;\n });\n\n if (applicableRules.length === 0) continue;\n\n const sourceResult = yield* fs.readFileString(absPath).pipe(Effect.result);\n if (sourceResult._tag === \"Failure\") continue;\n const source = sourceResult.success;\n\n const grammar = grammarForExtension(ext);\n if (!grammar) continue;\n\n const tree = yield* parserService.parse(source, grammar);\n\n const rulesForFile: Array<{\n ruleName: string;\n context: RuleContextImpl;\n visitors: Visitors;\n }> = [];\n\n for (const entry of applicableRules) {\n entry.context.setFile(absPath, source);\n const beforeResult = entry.visitors.before?.(absPath);\n if (beforeResult === false) continue;\n rulesForFile.push({\n ruleName: entry.name,\n context: entry.context,\n visitors: entry.visitors,\n });\n }\n\n if (rulesForFile.length === 0) continue;\n\n const fileFlags = walkFile(tree, rulesForFile);\n allFlags.push(...fileFlags);\n }\n\n for (const entry of ruleEntries) {\n entry.visitors.after?.();\n }\n\n return { flags: allFlags, noMatchingRules: false };\n});\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\nimport { FlagRecord } from \"../../domain/flag.js\";\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class CheckCommand extends Schema.TaggedClass<CheckCommand>()(\"CheckCommand\", {\n /** When `true`, scan all files instead of only git-changed files. */\n all: Schema.Boolean,\n /** Rule name filter. Empty array means \"run all rules\". */\n rules: Schema.Array(Schema.String),\n /** When `true`, suppress instruction and hint blocks in output. */\n dryRun: Schema.Boolean,\n /** Git ref to diff against. `undefined` means auto-detect. */\n base: Schema.UndefinedOr(Schema.String),\n /** Explicit file paths from positional CLI arguments. */\n files: Schema.Array(Schema.String),\n}) {}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class CheckResult extends Schema.TaggedClass<CheckResult>()(\"CheckResult\", {\n /** Unreviewed flags to display. */\n flags: Schema.Array(FlagRecord),\n /** Total flags before filtering out reviewed ones. */\n totalFlags: Schema.Number,\n /** Number of flags filtered out because they were previously reviewed. */\n filteredCount: Schema.Number,\n /** `true` when the `--rule` filter matched no registered rules. */\n noMatchingRules: Schema.Boolean,\n /** Available rule names (for error messages when no match). */\n availableRules: Schema.Array(Schema.String),\n}) {}\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, HashSet } from \"effect\";\nimport { ConfigLoader } from \"../../shared/infrastructure/config-loader.js\";\nimport { StateStore } from \"../../shared/infrastructure/state-store.js\";\nimport { collectFlags } from \"../../shared/pipeline/collect-flags.js\";\nimport { CheckCommand, CheckResult } from \"./request.js\";\n\n/** @since 0.1.0 */\nexport const checkHandler = Effect.fn(\"checkHandler\")(function* (command: CheckCommand) {\n const configLoader = yield* ConfigLoader;\n const stateStore = yield* StateStore;\n\n const config = yield* configLoader.load();\n const availableRules = Object.keys(config.rules);\n\n const result = yield* collectFlags({\n all: command.all,\n rules: command.rules,\n dryRun: command.dryRun,\n base: command.base,\n files: command.files,\n });\n\n if (result.noMatchingRules) {\n return new CheckResult({\n flags: [],\n totalFlags: 0,\n filteredCount: 0,\n noMatchingRules: true,\n availableRules,\n });\n }\n\n const allFlags = result.flags;\n\n if (allFlags.length === 0) {\n return new CheckResult({\n flags: [],\n totalFlags: 0,\n filteredCount: 0,\n noMatchingRules: false,\n availableRules,\n });\n }\n\n const reviewed = yield* stateStore.load();\n const reviewedSize = HashSet.size(reviewed);\n const filteredCount = reviewedSize > 0 ? allFlags.filter((f) => HashSet.has(reviewed, f.hash)).length : 0;\n const unreviewedFlags = reviewedSize > 0 ? allFlags.filter((f) => !HashSet.has(reviewed, f.hash)) : allFlags;\n\n return new CheckResult({\n flags: unreviewedFlags,\n totalFlags: allFlags.length,\n filteredCount,\n noMatchingRules: false,\n availableRules,\n });\n});\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class InitCommand extends Schema.TaggedClass<InitCommand>()(\"InitCommand\", {}) {}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class InitResult extends Schema.TaggedClass<InitResult>()(\"InitResult\", {\n /** Whether a new config file was created. */\n created: Schema.Boolean,\n /** Human-readable message describing what happened. */\n message: Schema.String,\n}) {}\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, FileSystem, Path } from \"effect\";\nimport { Env } from \"../../config/env.js\";\nimport { InitCommand, InitResult } from \"./request.js\";\n\n/**\n * Minimal starter config written by `agentreview init`.\n *\n * @since 0.1.0\n * @category constants\n */\nconst STARTER_CONFIG = `import { defineConfig } from \"agentreview\"\n\nexport default defineConfig({\n include: [\"src/**/*.{ts,tsx}\"],\n rules: {},\n})\n`;\n\nconst SKILLS_ADD_CMD = \"npx skills@latest add aurelienbobenrieth/agentreview\";\nconst INTENT_INSTALL_CMD = \"npx @tanstack/intent install\";\n\n/**\n * Detect which skill installation method is most likely appropriate.\n *\n * Checks for TanStack Intent or existing AGENTS.md with intent block.\n */\nconst detectSkillMethod = Effect.fn(\"detectSkillMethod\")(function* (cwd: string) {\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n\n // Check if TanStack Intent is installed\n const hasIntent = yield* fs\n .exists(path.resolve(cwd, \"node_modules/@tanstack/intent\"))\n .pipe(Effect.orElseSucceed(() => false));\n\n if (hasIntent) return \"intent\" as const;\n\n // Check if AGENTS.md has an intent-skills block (installed by intent previously)\n const agentsPath = path.resolve(cwd, \"AGENTS.md\");\n if (yield* fs.exists(agentsPath)) {\n const content = yield* fs.readFileString(agentsPath);\n if (content.includes(\"intent-skills:start\")) return \"intent\" as const;\n }\n\n return \"skills\" as const;\n});\n\n/** @since 0.1.0 */\nexport const initHandler = Effect.fn(\"initHandler\")(function* (_command: InitCommand) {\n const env = yield* Env;\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const configPath = path.resolve(env.cwd, \"agentreview.config.ts\");\n\n const gitignorePath = path.resolve(env.cwd, \".gitignore\");\n\n // --- Step 1: Create config ---\n const configCreated = !(yield* fs.exists(configPath));\n if (configCreated) {\n yield* fs.writeFileString(configPath, STARTER_CONFIG);\n }\n\n // --- Step 2: Ensure .agentreview-state is gitignored ---\n let gitignoreUpdated = false;\n const gitignoreExists = yield* fs.exists(gitignorePath);\n if (gitignoreExists) {\n const content = yield* fs.readFileString(gitignorePath);\n if (!content.includes(\".agentreview-state\")) {\n const separator = content.endsWith(\"\\n\") ? \"\" : \"\\n\";\n yield* fs.writeFileString(\n gitignorePath,\n content + separator + \"\\n# agentreview local state\\n.agentreview-state\\n\",\n );\n gitignoreUpdated = true;\n }\n } else {\n yield* fs.writeFileString(gitignorePath, \"# agentreview local state\\n.agentreview-state\\n\");\n gitignoreUpdated = true;\n }\n\n const lines: Array<string> = [];\n\n if (configCreated) {\n lines.push(\"✓ Created agentreview.config.ts\");\n } else {\n lines.push(\"· agentreview.config.ts already exists — skipped\");\n }\n\n if (gitignoreUpdated) {\n lines.push(\"✓ Added .agentreview-state to .gitignore\");\n }\n\n // --- Step 2: Next steps ---\n const method = yield* detectSkillMethod(env.cwd);\n const skillCmd = method === \"intent\" ? INTENT_INSTALL_CMD : SKILLS_ADD_CMD;\n\n lines.push(\n \"\",\n \"Next steps:\",\n \" 1. Add rules to your config\",\n ` 2. Install the agentreview skill for your AI agents:`,\n ` ${skillCmd}`,\n \" 3. Run: npx agentreview check --all\",\n );\n\n return new InitResult({\n created: configCreated,\n message: lines.join(\"\\n\"),\n });\n});\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport const RuleSummary = Schema.Struct({\n name: Schema.String,\n description: Schema.String,\n languages: Schema.Array(Schema.String),\n include: Schema.UndefinedOr(Schema.Array(Schema.String)),\n ignore: Schema.UndefinedOr(Schema.Array(Schema.String)),\n});\n\n/** @since 0.1.0 */\nexport type RuleSummary = Schema.Schema.Type<typeof RuleSummary>;\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class ListCommand extends Schema.TaggedClass<ListCommand>()(\"ListCommand\", {}) {}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class ListResult extends Schema.TaggedClass<ListResult>()(\"ListResult\", {\n /** All registered rules with their metadata. */\n rules: Schema.Array(RuleSummary),\n}) {}\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Effect } from \"effect\";\nimport { ConfigLoader } from \"../../shared/infrastructure/config-loader.js\";\nimport { ListCommand, ListResult } from \"./request.js\";\n\n/** @since 0.1.0 */\nexport const listHandler = Effect.fn(\"listHandler\")(function* (_command: ListCommand) {\n const configLoader = yield* ConfigLoader;\n const config = yield* configLoader.load();\n\n const rules = Object.entries(config.rules).map(([name, rule]) => ({\n name,\n description: rule.meta.description,\n languages: rule.meta.languages,\n include: rule.meta.include,\n ignore: rule.meta.ignore,\n }));\n\n return new ListResult({ rules });\n});\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class ReviewCommand extends Schema.TaggedClass<ReviewCommand>()(\"ReviewCommand\", {\n /** Specific hashes to mark as reviewed. */\n hashes: Schema.Array(Schema.String),\n /** When `true`, mark all current flags as reviewed. */\n all: Schema.Boolean,\n /** When `true`, wipe the state file. */\n reset: Schema.Boolean,\n}) {}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport class ReviewResult extends Schema.TaggedClass<ReviewResult>()(\"ReviewResult\", {\n /** Human-readable message describing what happened. */\n message: Schema.String,\n}) {}\n","/**\n * @module\n * @since 0.1.0\n */\n\nimport { Effect } from \"effect\";\nimport { StateStore } from \"../../shared/infrastructure/state-store.js\";\nimport { collectFlags } from \"../../shared/pipeline/collect-flags.js\";\nimport { ReviewCommand, ReviewResult } from \"./request.js\";\n\n/** @since 0.1.0 */\nexport const reviewHandler = Effect.fn(\"reviewHandler\")(function* (command: ReviewCommand) {\n const stateStore = yield* StateStore;\n\n if (command.reset) {\n yield* stateStore.reset();\n return new ReviewResult({ message: \"Cleared .agentreview-state\" });\n }\n\n if (command.all) {\n const result = yield* collectFlags({\n all: true,\n rules: [],\n dryRun: false,\n base: undefined,\n files: [],\n });\n const allFlags = result.flags;\n if (allFlags.length === 0) {\n return new ReviewResult({ message: \"No flags to review.\" });\n }\n yield* stateStore.append(allFlags.map((f) => f.hash));\n return new ReviewResult({ message: `Marked ${allFlags.length} flag(s) as reviewed.` });\n }\n\n if (command.hashes.length > 0) {\n yield* stateStore.append([...command.hashes]);\n return new ReviewResult({ message: `Marked ${command.hashes.length} hash(es) as reviewed.` });\n }\n\n return new ReviewResult({\n message: [\n \"Usage:\",\n \" agentreview review <hash...> Mark specific flags as reviewed\",\n \" agentreview review --all Mark all current flags as reviewed\",\n \" agentreview review --reset Wipe the state file\",\n ].join(\"\\n\"),\n });\n});\n","/**\n * Terminal reporter — formats flag results into human-readable output.\n *\n * Respects `NO_COLOR` and non-TTY environments. Groups flags by rule,\n * then by file, and appends instruction/hint blocks when available.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Effect, HashMap, Option, Path, Schema } from \"effect\";\nimport { Env } from \"../config/env.js\";\nimport type { FlagRecord } from \"../domain/flag.js\";\nimport type { RuleMeta } from \"../domain/rule.js\";\n\n/**\n * Group an array by a key function, returning a HashMap of arrays.\n *\n * @since 0.1.0\n * @category internals\n */\nfunction groupBy<A>(items: ReadonlyArray<A>, key: (a: A) => string): HashMap.HashMap<string, A[]> {\n return items.reduce((acc, item) => {\n const k = key(item);\n const existing = Option.getOrUndefined(HashMap.get(acc, k));\n return existing ? (existing.push(item), acc) : HashMap.set(acc, k, [item]);\n }, HashMap.empty<string, A[]>());\n}\n\n/**\n * Build the minimal ANSI escape helpers. Each function is a no-op when\n * `noColor` is `true`.\n *\n * @since 0.1.0\n * @category internals\n */\nfunction makeAnsi(noColor: boolean) {\n return {\n bold: (s: string) => (noColor ? s : `\\x1b[1m${s}\\x1b[22m`),\n dim: (s: string) => (noColor ? s : `\\x1b[2m${s}\\x1b[22m`),\n yellow: (s: string) => (noColor ? s : `\\x1b[33m${s}\\x1b[39m`),\n cyan: (s: string) => (noColor ? s : `\\x1b[36m${s}\\x1b[39m`),\n magenta: (s: string) => (noColor ? s : `\\x1b[35m${s}\\x1b[39m`),\n gray: (s: string) => (noColor ? s : `\\x1b[90m${s}\\x1b[39m`),\n underline: (s: string) => (noColor ? s : `\\x1b[4m${s}\\x1b[24m`),\n reset: noColor ? \"\" : \"\\x1b[0m\",\n };\n}\n\n/**\n * Options that control the reporter's output format.\n *\n * @since 0.1.0\n * @category models\n */\nexport const ReporterOptions = Schema.Struct({\n /** When `true`, instruction and hint blocks are suppressed. */\n dryRun: Schema.Boolean,\n /** Version string displayed in the header line. */\n version: Schema.String,\n});\n\n/** @since 0.1.0 */\nexport type ReporterOptions = Schema.Schema.Type<typeof ReporterOptions>;\n\n/**\n * Format flag results into a terminal-friendly report string.\n *\n * Groups flags by rule name, then by file path. Includes source\n * snippets, per-match instructions/hints, and a summary line.\n * Returns a single \"no rules triggered\" line when the flag list\n * is empty.\n *\n * Uses the `Env` service for colour/cwd detection and the Effect\n * `Path` service for cross-platform path resolution.\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const formatReport = Effect.fn(\"formatReport\")(function* (\n flags: ReadonlyArray<FlagRecord>,\n rulesMeta: HashMap.HashMap<string, RuleMeta>,\n options: ReporterOptions,\n) {\n const env = yield* Env;\n const path = yield* Path.Path;\n const ansi = makeAnsi(env.noColor);\n\n if (flags.length === 0) {\n return `${ansi.bold(\"agentreview\")} ${ansi.dim(`v${options.version}`)} ${ansi.dim(\"-\")} no rules triggered.`;\n }\n\n const { cwd } = env;\n const lines: string[] = [];\n\n const grouped = groupBy(flags, (f) => f.ruleName);\n\n const groupedSize = HashMap.size(grouped);\n\n if (flags.length > 50) {\n lines.push(\n ansi.yellow(\"⚠\") +\n ` ${flags.length} matches across ${groupedSize} rules. ` +\n ansi.dim(\"Consider narrowing scope with --rule or targeting specific files.\"),\n );\n lines.push(\"\");\n }\n\n for (const [ruleName, ruleFlags] of grouped) {\n const meta = Option.getOrUndefined(HashMap.get(rulesMeta, ruleName));\n\n lines.push(ansi.yellow(` x ${ruleName}`) + ansi.dim(meta ? `: ${meta.description}` : \"\"));\n lines.push(\"\");\n\n const byFile = groupBy(ruleFlags, (f) => path.relative(cwd, f.filename).replace(/\\\\/g, \"/\"));\n\n for (const [_filePath, fileFlags] of byFile) {\n for (const flag of fileFlags) {\n const relPath = path.relative(cwd, flag.filename).replace(/\\\\/g, \"/\");\n const loc = `${relPath}:${flag.line}:${flag.col}`;\n const snippet = flag.sourceSnippet.length > 80 ? flag.sourceSnippet.slice(0, 77) + \"...\" : flag.sourceSnippet;\n\n lines.push(` ${ansi.cyan(loc)} ${ansi.dim(`[${flag.hash}]`)} ${flag.message}`);\n if (snippet && snippet !== flag.message) {\n lines.push(` ${ansi.dim(snippet)}`);\n }\n lines.push(\"\");\n }\n }\n\n if (!options.dryRun && meta?.instruction) {\n lines.push(ansi.dim(\" ┌─ Instruction ─────────────────────────────────\"));\n for (const instrLine of meta.instruction.split(\"\\n\")) {\n lines.push(ansi.dim(` │ ${instrLine}`));\n }\n lines.push(ansi.dim(\" └───────────────────────────────────────────────\"));\n lines.push(\"\");\n }\n\n const matchNotes = ruleFlags.filter((f) => f.instruction || f.suggest);\n if (!options.dryRun && matchNotes.length > 0) {\n for (const flag of matchNotes) {\n const relPath = path.relative(cwd, flag.filename).replace(/\\\\/g, \"/\");\n if (flag.instruction) {\n lines.push(` ${ansi.magenta(\"note\")} ${ansi.dim(`${relPath}:${flag.line}`)} ${flag.instruction}`);\n }\n if (flag.suggest) {\n lines.push(` ${ansi.magenta(\"hint\")} ${ansi.dim(`${relPath}:${flag.line}`)} ${flag.suggest}`);\n }\n }\n lines.push(\"\");\n }\n }\n\n const ruleWord = groupedSize === 1 ? \"rule\" : \"rules\";\n const matchWord = flags.length === 1 ? \"match\" : \"matches\";\n lines.push(ansi.bold(ansi.yellow(`Found ${flags.length} ${matchWord}`)) + ansi.dim(` (${groupedSize} ${ruleWord})`));\n\n return lines.join(\"\\n\");\n});\n","#!/usr/bin/env node\n/**\n * CLI entry point for `agentreview`.\n *\n * Thin adapter that translates CLI arguments into feature commands,\n * dispatches to the appropriate handler, and formats the result\n * for terminal output.\n *\n * @module\n * @since 0.1.0\n */\n\nimport * as NodeRuntime from \"@effect/platform-node/NodeRuntime\";\nimport * as NodeServices from \"@effect/platform-node/NodeServices\";\nimport { Console, Effect, HashMap, Layer, Option } from \"effect\";\nimport { Argument, Command, Flag } from \"effect/unstable/cli\";\nimport { checkHandler } from \"./features/check/handler.js\";\nimport { CheckCommand } from \"./features/check/request.js\";\nimport { initHandler } from \"./features/init/handler.js\";\nimport { InitCommand } from \"./features/init/request.js\";\nimport { listHandler } from \"./features/list/handler.js\";\nimport { ListCommand } from \"./features/list/request.js\";\nimport { reviewHandler } from \"./features/review/handler.js\";\nimport { ReviewCommand } from \"./features/review/request.js\";\nimport { Env } from \"./config/env.js\";\nimport { ConfigLoader } from \"./shared/infrastructure/config-loader.js\";\nimport { Git } from \"./shared/infrastructure/git.js\";\nimport { IgnoreReader } from \"./shared/infrastructure/ignore-reader.js\";\nimport { Parser } from \"./shared/infrastructure/parser.js\";\nimport { StateStore } from \"./shared/infrastructure/state-store.js\";\nimport { formatReport } from \"./cli/reporter.js\";\nimport type { RuleMeta } from \"./domain/rule.js\";\n\ndeclare const __AGENTREVIEW_VERSION__: string;\n\n/** The `check` subcommand — scans files and outputs a report. */\nconst check = Command.make(\n \"check\",\n {\n files: Argument.string(\"files\").pipe(\n Argument.withDescription(\"Specific files or globs to scan\"),\n Argument.variadic(),\n ),\n all: Flag.boolean(\"all\").pipe(Flag.withAlias(\"a\"), Flag.withDescription(\"Scan all files (not just git diff)\")),\n rule: Flag.string(\"rule\").pipe(\n Flag.withAlias(\"r\"),\n Flag.withDescription(\"Run only this rule (comma-separated for multiple)\"),\n Flag.optional,\n ),\n dryRun: Flag.boolean(\"dry-run\").pipe(\n Flag.withAlias(\"d\"),\n Flag.withDescription(\"Show counts only, no instruction blocks\"),\n ),\n base: Flag.string(\"base\").pipe(Flag.withDescription(\"Git ref to diff against\"), Flag.optional),\n },\n (config) => {\n const ruleFilter = Option.match(config.rule, {\n onNone: () => [] as ReadonlyArray<string>,\n onSome: (r: string) => r.split(\",\").map((s) => s.trim()),\n });\n const baseRef = Option.match(config.base, {\n onNone: () => undefined,\n onSome: (b: string) => b,\n });\n\n return Effect.gen(function* () {\n const env = yield* Env;\n const result = yield* checkHandler(\n new CheckCommand({\n all: config.all,\n rules: ruleFilter,\n dryRun: config.dryRun,\n base: baseRef,\n files: config.files,\n }),\n );\n\n if (result.noMatchingRules) {\n yield* Console.log(`No matching rules found. Available: ${result.availableRules.join(\", \")}`);\n return;\n }\n\n if (result.totalFlags === 0) {\n yield* Console.log(`agentreview v${__AGENTREVIEW_VERSION__} - no rules triggered.`);\n return;\n }\n\n const configLoader = yield* ConfigLoader;\n const cfg = yield* configLoader.load();\n const rulesMeta: HashMap.HashMap<string, RuleMeta> = HashMap.fromIterable(\n Object.entries(cfg.rules).map(([name, rule]) => [name, rule.meta] as const),\n );\n\n const output = yield* formatReport(result.flags, rulesMeta, {\n dryRun: config.dryRun,\n version: __AGENTREVIEW_VERSION__,\n });\n\n yield* Console.log(output);\n\n if (result.filteredCount > 0) {\n yield* Console.log(\n ` (${result.filteredCount} reviewed flag(s) hidden — run agentreview review --reset to clear)`,\n );\n }\n\n if (result.flags.length > 0) {\n env.setExitCode(1);\n }\n });\n },\n).pipe(Command.withDescription(\"Scan files and output report for AI agents\"));\n\n/** The `list` subcommand — prints all registered rules. */\nconst list = Command.make(\"list\", {}, () =>\n Effect.gen(function* () {\n const result = yield* listHandler(new ListCommand({}));\n\n if (result.rules.length === 0) {\n yield* Console.log(\"No rules registered.\");\n return;\n }\n\n yield* Console.log(`${result.rules.length} rule(s) registered:\\n`);\n\n for (const rule of result.rules) {\n const langs = rule.languages.join(\", \");\n yield* Console.log(` ${rule.name}`);\n yield* Console.log(` ${rule.description}`);\n yield* Console.log(` Languages: ${langs}`);\n if (rule.include) {\n yield* Console.log(` Include: ${rule.include.join(\", \")}`);\n }\n if (rule.ignore) {\n yield* Console.log(` Ignore: ${rule.ignore.join(\", \")}`);\n }\n yield* Console.log();\n }\n }),\n).pipe(Command.withDescription(\"List all registered rules\"));\n\n/** The `init` subcommand — scaffolds a starter config file. */\nconst init = Command.make(\"init\", {}, () =>\n Effect.gen(function* () {\n const result = yield* initHandler(new InitCommand({}));\n yield* Console.log(result.message);\n }),\n).pipe(Command.withDescription(\"Create agentreview.config.ts and set up agent skill discovery\"));\n\n/** The `review` subcommand — manage reviewed-flag state. */\nconst review = Command.make(\n \"review\",\n {\n hashes: Argument.string(\"hashes\").pipe(\n Argument.withDescription(\"Flag hashes to mark as reviewed\"),\n Argument.variadic(),\n ),\n all: Flag.boolean(\"all\").pipe(Flag.withAlias(\"a\"), Flag.withDescription(\"Mark all current flags as reviewed\")),\n reset: Flag.boolean(\"reset\").pipe(Flag.withDescription(\"Wipe the state file\")),\n },\n (config) =>\n Effect.gen(function* () {\n const result = yield* reviewHandler(\n new ReviewCommand({\n hashes: config.hashes,\n all: config.all,\n reset: config.reset,\n }),\n );\n yield* Console.log(result.message);\n }),\n).pipe(Command.withDescription(\"Mark flags as reviewed (filters them from check output)\"));\n\nconst agentreview = Command.make(\"agentreview\").pipe(\n Command.withDescription(\"Deterministic linting for AI agents\"),\n Command.withSubcommands([check, list, init, review]),\n);\n\nconst AppLayer = Layer.mergeAll(ConfigLoader.layer, Parser.layer, Git.layer, IgnoreReader.layer, StateStore.layer).pipe(\n Layer.provideMerge(NodeServices.layer),\n Layer.provideMerge(Env.layer),\n);\n\n// The CLI framework uses `unknown` for aggregated service requirements,\n// which doesn't fully resolve via `Effect.provide`. Cast to satisfy\n// `NodeRuntime.runMain`'s `never` requirement constraint.\nconst program = Command.run(agentreview, { version: __AGENTREVIEW_VERSION__ }).pipe(\n Effect.provide(AppLayer),\n) as Effect.Effect<void>;\n\nNodeRuntime.runMain(program);\n"],"mappings":"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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/domain/node.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* 0-indexed position in source text.
|
|
5
|
+
*
|
|
6
|
+
* Defined as a `Schema.Struct` so positions can be decoded and
|
|
7
|
+
* validated from external sources if needed.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
* @category models
|
|
11
|
+
*/
|
|
12
|
+
declare const Position: Schema.Struct<{
|
|
13
|
+
readonly row: Schema.Number;
|
|
14
|
+
readonly column: Schema.Number;
|
|
15
|
+
}>;
|
|
16
|
+
/** @since 0.1.0 */
|
|
17
|
+
type Position = Schema.Schema.Type<typeof Position>;
|
|
18
|
+
/**
|
|
19
|
+
* Read-only view of a syntax tree node.
|
|
20
|
+
*
|
|
21
|
+
* One universal type — no per-kind subtypes. Rules narrow via
|
|
22
|
+
* `node.type` string checks and field accessors.
|
|
23
|
+
*
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
* @category models
|
|
26
|
+
*/
|
|
27
|
+
interface AgentReviewNode {
|
|
28
|
+
/** tree-sitter grammar node type (e.g. `"function_declaration"`, `"comment"`) */
|
|
29
|
+
readonly type: string;
|
|
30
|
+
/** Full source text covered by this node. */
|
|
31
|
+
readonly text: string;
|
|
32
|
+
/** 0-indexed start position. */
|
|
33
|
+
readonly startPosition: Position;
|
|
34
|
+
/** 0-indexed end position. */
|
|
35
|
+
readonly endPosition: Position;
|
|
36
|
+
/** Whether this is a named node in the grammar. */
|
|
37
|
+
readonly isNamed: boolean;
|
|
38
|
+
/** Direct child nodes (lazily wrapped). */
|
|
39
|
+
readonly children: ReadonlyArray<AgentReviewNode>;
|
|
40
|
+
/** Parent node, or null for the root. */
|
|
41
|
+
readonly parent: AgentReviewNode | null;
|
|
42
|
+
/** Number of direct children. */
|
|
43
|
+
readonly childCount: number;
|
|
44
|
+
/** Get a child by its grammar field name (e.g. `"name"`, `"body"`). */
|
|
45
|
+
childByFieldName(name: string): AgentReviewNode | null;
|
|
46
|
+
/** All direct children matching the given node type. */
|
|
47
|
+
childrenByType(type: string): ReadonlyArray<AgentReviewNode>;
|
|
48
|
+
/** Recursively collect all descendants matching the given node type. */
|
|
49
|
+
descendantsOfType(type: string): ReadonlyArray<AgentReviewNode>;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/domain/node-types.d.ts
|
|
53
|
+
/**
|
|
54
|
+
* Generated from tree-sitter-typescript's `node-types.json`.
|
|
55
|
+
* Provides autocomplete for visitor keys in {@link Visitors}.
|
|
56
|
+
*
|
|
57
|
+
* @module
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Named node types from the tree-sitter TypeScript/TSX grammar.
|
|
62
|
+
*
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
* @category models
|
|
65
|
+
*/
|
|
66
|
+
type TreeSitterNodeType = "abstract_class_declaration" | "abstract_method_signature" | "accessibility_modifier" | "ambient_declaration" | "arguments" | "array" | "array_pattern" | "array_type" | "arrow_function" | "as_expression" | "assignment_expression" | "assignment_pattern" | "augmented_assignment_expression" | "await_expression" | "binary_expression" | "break_statement" | "call_expression" | "call_signature" | "catch_clause" | "class" | "class_body" | "class_declaration" | "class_heritage" | "class_static_block" | "comment" | "computed_property_name" | "conditional_type" | "construct_signature" | "constructor_type" | "continue_statement" | "debugger_statement" | "decorator" | "default_type" | "do_statement" | "else_clause" | "empty_statement" | "enum_assignment" | "enum_body" | "enum_declaration" | "escape_sequence" | "export_clause" | "export_specifier" | "export_statement" | "expression_statement" | "extends_clause" | "extends_type_clause" | "false" | "finally_clause" | "for_in_statement" | "for_statement" | "formal_parameters" | "function_declaration" | "function_expression" | "function_signature" | "function_type" | "generator_function" | "generator_function_declaration" | "generic_type" | "hash_bang_line" | "identifier" | "if_statement" | "implements_clause" | "import" | "import_alias" | "import_attribute" | "import_clause" | "import_require_clause" | "import_specifier" | "import_statement" | "index_signature" | "index_type_query" | "infer_type" | "instantiation_expression" | "interface_body" | "interface_declaration" | "internal_module" | "intersection_type" | "labeled_statement" | "lexical_declaration" | "literal_type" | "lookup_type" | "mapped_type_clause" | "member_expression" | "meta_property" | "method_definition" | "method_signature" | "module" | "named_imports" | "namespace_export" | "namespace_import" | "nested_identifier" | "nested_type_identifier" | "new_expression" | "non_null_expression" | "null" | "number" | "object" | "object_assignment_pattern" | "object_pattern" | "object_type" | "optional_parameter" | "optional_type" | "override_modifier" | "pair" | "pair_pattern" | "parenthesized_expression" | "parenthesized_type" | "predefined_type" | "private_property_identifier" | "program" | "property_identifier" | "property_signature" | "public_field_definition" | "readonly_type" | "regex" | "regex_flags" | "regex_pattern" | "required_parameter" | "rest_pattern" | "rest_type" | "return_statement" | "satisfies_expression" | "sequence_expression" | "shorthand_property_identifier" | "shorthand_property_identifier_pattern" | "spread_element" | "statement_block" | "string" | "string_fragment" | "subscript_expression" | "super" | "switch_body" | "switch_case" | "switch_default" | "switch_statement" | "template_literal_type" | "template_string" | "template_substitution" | "template_type" | "ternary_expression" | "this" | "this_type" | "throw_statement" | "true" | "try_statement" | "tuple_type" | "type_alias_declaration" | "type_annotation" | "type_arguments" | "type_assertion" | "type_identifier" | "type_parameter" | "type_parameters" | "type_predicate" | "type_predicate_annotation" | "type_query" | "unary_expression" | "undefined" | "union_type" | "update_expression" | "variable_declaration" | "variable_declarator" | "while_statement" | "with_statement" | "yield_expression" | "jsx_attribute" | "jsx_closing_element" | "jsx_element" | "jsx_expression" | "jsx_fragment" | "jsx_namespace_name" | "jsx_opening_element" | "jsx_self_closing_element" | "jsx_text";
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/domain/flag.d.ts
|
|
69
|
+
/**
|
|
70
|
+
* Options passed to `context.flag()` by rule visitors.
|
|
71
|
+
*
|
|
72
|
+
* @since 0.1.0
|
|
73
|
+
* @category models
|
|
74
|
+
*/
|
|
75
|
+
interface FlagOptions {
|
|
76
|
+
/** The AST node that triggered the match. */
|
|
77
|
+
readonly node: AgentReviewNode;
|
|
78
|
+
/** Short one-liner displayed next to file:line in output. */
|
|
79
|
+
readonly message: string;
|
|
80
|
+
/**
|
|
81
|
+
* Override `meta.instruction` for this specific match.
|
|
82
|
+
* Appears inline in per-match notes.
|
|
83
|
+
*/
|
|
84
|
+
readonly instruction?: string | undefined;
|
|
85
|
+
/** Hint toward the fix. Not a command - just a nudge. */
|
|
86
|
+
readonly suggest?: string | undefined;
|
|
87
|
+
}
|
|
88
|
+
declare const FlagRecord_base: Schema.ExtendableClass<FlagRecord, Schema.Struct<{
|
|
89
|
+
readonly ruleName: Schema.String;
|
|
90
|
+
readonly filename: Schema.String; /** 1-based line number. */
|
|
91
|
+
readonly line: Schema.Number; /** 1-based column number. */
|
|
92
|
+
readonly col: Schema.Number;
|
|
93
|
+
readonly message: Schema.String;
|
|
94
|
+
readonly sourceSnippet: Schema.String; /** 7-char hex hash for stable match identification. */
|
|
95
|
+
readonly hash: Schema.String;
|
|
96
|
+
readonly instruction: Schema.UndefinedOr<Schema.String>;
|
|
97
|
+
readonly suggest: Schema.UndefinedOr<Schema.String>;
|
|
98
|
+
}>, {}>;
|
|
99
|
+
/**
|
|
100
|
+
* Enriched flag record after processing — ready for the reporter.
|
|
101
|
+
*
|
|
102
|
+
* Uses `Schema.Class` for structural equality AND runtime validation
|
|
103
|
+
* on construction — invalid fields throw a clear `ParseError`.
|
|
104
|
+
*
|
|
105
|
+
* @since 0.1.0
|
|
106
|
+
* @category models
|
|
107
|
+
*/
|
|
108
|
+
declare class FlagRecord extends FlagRecord_base {}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/domain/rule-context.d.ts
|
|
111
|
+
/**
|
|
112
|
+
* Context object passed to `createOnce`. Available throughout the rule's lifecycle.
|
|
113
|
+
*
|
|
114
|
+
* @since 0.1.0
|
|
115
|
+
* @category models
|
|
116
|
+
*/
|
|
117
|
+
interface RuleContext {
|
|
118
|
+
/** Absolute path of the current file being analyzed. */
|
|
119
|
+
getFilename(): string;
|
|
120
|
+
/** Full source content of the current file. */
|
|
121
|
+
getSourceCode(): string;
|
|
122
|
+
/**
|
|
123
|
+
* Lines around the given 1-based line number, formatted with line numbers.
|
|
124
|
+
* @param line 1-based line number
|
|
125
|
+
* @param radius number of lines above/below to include (default 10)
|
|
126
|
+
*/
|
|
127
|
+
getLinesAround(line: number, radius?: number): string;
|
|
128
|
+
/** Record a match for the output report. */
|
|
129
|
+
flag(options: FlagOptions): void;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/domain/rule.d.ts
|
|
133
|
+
/**
|
|
134
|
+
* Static metadata for a rule.
|
|
135
|
+
*
|
|
136
|
+
* Defined as a `Schema.Struct` so that rule metadata is validated at
|
|
137
|
+
* runtime when `defineRule` is called — catches typos, missing fields,
|
|
138
|
+
* and wrong types with clear error messages.
|
|
139
|
+
*
|
|
140
|
+
* @since 0.1.0
|
|
141
|
+
* @category models
|
|
142
|
+
*/
|
|
143
|
+
declare const RuleMeta: Schema.Struct<{
|
|
144
|
+
/** Unique identifier. kebab-case. Used in output, --rule filtering, agentreview-ignore. */readonly name: Schema.String; /** One-liner explaining what the rule checks. */
|
|
145
|
+
readonly description: Schema.String; /** File extensions this rule applies to, without the dot. e.g. `["ts", "tsx"]` */
|
|
146
|
+
readonly languages: Schema.$Array<Schema.String>;
|
|
147
|
+
/**
|
|
148
|
+
* Natural language instruction for the calling AI agent.
|
|
149
|
+
* Defines pass/fail criteria and how to evaluate flagged matches.
|
|
150
|
+
*/
|
|
151
|
+
readonly instruction: Schema.String; /** If provided, rule only runs on files matching these globs (after global filtering). */
|
|
152
|
+
readonly include: Schema.optional<Schema.$Array<Schema.String>>; /** If provided, files matching these globs are excluded from this rule. */
|
|
153
|
+
readonly ignore: Schema.optional<Schema.$Array<Schema.String>>;
|
|
154
|
+
}>;
|
|
155
|
+
/** @since 0.1.0 */
|
|
156
|
+
type RuleMeta = Schema.Schema.Type<typeof RuleMeta>;
|
|
157
|
+
/**
|
|
158
|
+
* Callback invoked when a matching AST node type is visited.
|
|
159
|
+
*
|
|
160
|
+
* @since 0.1.0
|
|
161
|
+
* @category models
|
|
162
|
+
*/
|
|
163
|
+
type VisitorHandler = (node: AgentReviewNode) => void;
|
|
164
|
+
/**
|
|
165
|
+
* Visitor object returned by `createOnce`.
|
|
166
|
+
*
|
|
167
|
+
* Maps tree-sitter node type strings to handler functions.
|
|
168
|
+
* Known node types provide autocomplete; any string is accepted.
|
|
169
|
+
*
|
|
170
|
+
* @since 0.1.0
|
|
171
|
+
* @category models
|
|
172
|
+
*/
|
|
173
|
+
type Visitors = {
|
|
174
|
+
/**
|
|
175
|
+
* Called once before each file is traversed.
|
|
176
|
+
* Return `false` to skip this file entirely for this rule.
|
|
177
|
+
*/
|
|
178
|
+
before?: ((filename: string) => boolean | void) | undefined;
|
|
179
|
+
/**
|
|
180
|
+
* Called once after all files have been visited.
|
|
181
|
+
* Use for aggregate analysis.
|
|
182
|
+
*/
|
|
183
|
+
after?: (() => void) | undefined;
|
|
184
|
+
} & { [K in TreeSitterNodeType]?: VisitorHandler } & {
|
|
185
|
+
[nodeType: string]: VisitorHandler | ((filename: string) => boolean | void) | (() => void) | undefined;
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* A complete rule definition.
|
|
189
|
+
*
|
|
190
|
+
* @since 0.1.0
|
|
191
|
+
* @category models
|
|
192
|
+
*/
|
|
193
|
+
interface AgentReviewRule {
|
|
194
|
+
readonly meta: RuleMeta;
|
|
195
|
+
/**
|
|
196
|
+
* Called once per agentreview run (not per file).
|
|
197
|
+
* The returned visitor object is reused across files.
|
|
198
|
+
* Per-file state must be reset in `before()`.
|
|
199
|
+
*/
|
|
200
|
+
readonly createOnce: (context: RuleContext) => Visitors;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Identity function that provides type inference and IDE support for rule definitions.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* import { defineRule } from "agentreview"
|
|
208
|
+
*
|
|
209
|
+
* export const myRule = defineRule({
|
|
210
|
+
* meta: {
|
|
211
|
+
* name: "my-rule",
|
|
212
|
+
* description: "Checks for something",
|
|
213
|
+
* languages: ["ts", "tsx"],
|
|
214
|
+
* instruction: "Evaluate whether ..."
|
|
215
|
+
* },
|
|
216
|
+
* createOnce(context) {
|
|
217
|
+
* return {
|
|
218
|
+
* comment(node) {
|
|
219
|
+
* context.flag({ node, message: "Found comment" })
|
|
220
|
+
* }
|
|
221
|
+
* }
|
|
222
|
+
* }
|
|
223
|
+
* })
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @since 0.1.0
|
|
227
|
+
* @category constructors
|
|
228
|
+
*/
|
|
229
|
+
declare function defineRule(rule: AgentReviewRule): AgentReviewRule;
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/domain/config.d.ts
|
|
232
|
+
/**
|
|
233
|
+
* Top-level configuration schema for `agentreview.config.ts`.
|
|
234
|
+
*
|
|
235
|
+
* @since 0.1.0
|
|
236
|
+
* @category models
|
|
237
|
+
*/
|
|
238
|
+
interface AgentReviewConfig {
|
|
239
|
+
/** Rule registry. Keys are kebab-case names used in output and `--rule` filtering. */
|
|
240
|
+
readonly rules: Record<string, AgentReviewRule>;
|
|
241
|
+
/** If provided, only files matching at least one pattern are scanned. */
|
|
242
|
+
readonly include?: ReadonlyArray<string> | undefined;
|
|
243
|
+
/** Files matching any pattern are excluded (merged with built-in defaults). */
|
|
244
|
+
readonly ignore?: ReadonlyArray<string> | undefined;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Identity function that provides type inference and IDE support for config files.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* import { defineConfig } from "agentreview"
|
|
252
|
+
*
|
|
253
|
+
* export default defineConfig({
|
|
254
|
+
* include: ["src/**\/*.ts"],
|
|
255
|
+
* rules: { "my-rule": myRule }
|
|
256
|
+
* })
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* @since 0.1.0
|
|
260
|
+
* @category constructors
|
|
261
|
+
*/
|
|
262
|
+
declare function defineConfig(config: AgentReviewConfig): AgentReviewConfig;
|
|
263
|
+
//#endregion
|
|
264
|
+
export { type AgentReviewConfig, type AgentReviewNode, type AgentReviewRule, type FlagOptions, FlagRecord, Position, type RuleContext, RuleMeta, type TreeSitterNodeType, type VisitorHandler, type Visitors, defineConfig, defineRule };
|
|
265
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/domain/node.ts","../src/domain/node-types.ts","../src/domain/flag.ts","../src/domain/rule-context.ts","../src/domain/rule.ts","../src/domain/config.ts"],"mappings":";;;;;;;;;;;cAuBa,QAAA,EAAQ,MAAA,CAAA,MAAA;EAAA;;;;KAMT,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,IAAA,QAAY,QAAA;;;;;;AAWjD;;;;UAAiB,eAAA;EAYkB;EAAA,SAVxB,IAAA;EAYQ;EAAA,SAVR,IAAA;EAiBmC;EAAA,SAfnC,aAAA,EAAe,QAAA;EAiBuB;EAAA,SAftC,WAAA,EAAa,QAAA;EAewB;EAAA,SAbrC,OAAA;EARA;EAAA,SAUA,QAAA,EAAU,aAAA,CAAc,eAAA;EANxB;EAAA,SAQA,MAAA,EAAQ,eAAA;EANR;EAAA,SAQA,UAAA;EANA;EAST,gBAAA,CAAiB,IAAA,WAAe,eAAA;EAPb;EASnB,cAAA,CAAe,IAAA,WAAe,aAAA,CAAc,eAAA;EAPnC;EAST,iBAAA,CAAkB,IAAA,WAAe,aAAA,CAAc,eAAA;AAAA;;;;;;;AAxCjD;;;;;;;;;KCTY,kBAAA;;;;;;;;;UCKK,WAAA;;WAEN,IAAA,EAAM,eAAA;;WAEN,OAAA;EFMC;;;;EAAA,SEDD,WAAA;EFCmB;EAAA,SECnB,OAAA;AAAA;AAAA,cACV,eAAA;;oCFS+B;EAAA,8BAMN;EAAA;;yCAQP;EAAA;;;;;;;;;;;;;cEZN,UAAA,SAAmB,eAAA;;;;;;;;;UCvBf,WAAA;;EAEf,WAAA;;EAEA,aAAA;;AHMF;;;;EGAE,cAAA,CAAe,IAAA,UAAc,MAAA;EHAD;EGE5B,IAAA,CAAK,OAAA,EAAS,WAAA;AAAA;;;;;;;;;;;AHFhB;;cIHa,QAAA,EAAQ,MAAA,CAAA,MAAA;EJGoB,yHAAX;EAAA,qCAAmB;EAAA;EAAQ;AAWzD;;;EAXyD,qCAmBjC;EAAA,iEAIH;EAAA;;;KIPT,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,IAAA,QAAY,QAAA;;;;;;;KAQrC,cAAA,IAAkB,IAAA,EAAM,eAAA;;;;;;;;;;KAWxB,QAAA;EJLV;;;;EIUA,MAAA,KAAW,QAAA;EJRmB;;;;EIa9B,KAAA;AAAA,YACU,kBAAA,IAAsB,cAAA;EAAA,CAC/B,QAAA,WAAmB,cAAA,KAAmB,QAAA;AAAA;;AH9DzC;;;;;UGuEiB,eAAA;EAAA,SACN,IAAA,EAAM,QAAA;;AFnEjB;;;;WEyEW,UAAA,GAAa,OAAA,EAAS,WAAA,KAAgB,QAAA;AAAA;;;;;;AF7DhD;;;;;;;;;;;;;;;;;;;;;;iBE2Fe,UAAA,CAAW,IAAA,EAAM,eAAA,GAAkB,eAAA;;;;;;;;;UCrGlC,iBAAA;;WAEN,KAAA,EAAO,MAAA,SAAe,eAAA;;WAEtB,OAAA,GAAU,aAAA;ELIT;EAAA,SKFD,MAAA,GAAS,aAAA;AAAA;;;;;;;;ALapB;;;;;;;;;iBKMgB,YAAA,CAAa,MAAA,EAAQ,iBAAA,GAAoB,iBAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { r as FlagRecord, t as Position } from "./node-yh9mLvnE.mjs";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
//#region src/domain/rule.ts
|
|
4
|
+
/**
|
|
5
|
+
* Rule definition types and the `defineRule` helper.
|
|
6
|
+
*
|
|
7
|
+
* A rule is a reusable lint check defined by {@link RuleMeta} (what to check)
|
|
8
|
+
* and a `createOnce` factory (how to check it). The factory returns a
|
|
9
|
+
* {@link Visitors} object whose keys are tree-sitter node types.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Static metadata for a rule.
|
|
16
|
+
*
|
|
17
|
+
* Defined as a `Schema.Struct` so that rule metadata is validated at
|
|
18
|
+
* runtime when `defineRule` is called — catches typos, missing fields,
|
|
19
|
+
* and wrong types with clear error messages.
|
|
20
|
+
*
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
* @category models
|
|
23
|
+
*/
|
|
24
|
+
const RuleMeta = Schema.Struct({
|
|
25
|
+
name: Schema.String,
|
|
26
|
+
description: Schema.String,
|
|
27
|
+
languages: Schema.Array(Schema.String),
|
|
28
|
+
instruction: Schema.String,
|
|
29
|
+
include: Schema.optional(Schema.Array(Schema.String)),
|
|
30
|
+
ignore: Schema.optional(Schema.Array(Schema.String))
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Identity function that provides type inference and IDE support for rule definitions.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { defineRule } from "agentreview"
|
|
38
|
+
*
|
|
39
|
+
* export const myRule = defineRule({
|
|
40
|
+
* meta: {
|
|
41
|
+
* name: "my-rule",
|
|
42
|
+
* description: "Checks for something",
|
|
43
|
+
* languages: ["ts", "tsx"],
|
|
44
|
+
* instruction: "Evaluate whether ..."
|
|
45
|
+
* },
|
|
46
|
+
* createOnce(context) {
|
|
47
|
+
* return {
|
|
48
|
+
* comment(node) {
|
|
49
|
+
* context.flag({ node, message: "Found comment" })
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* })
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @since 0.1.0
|
|
57
|
+
* @category constructors
|
|
58
|
+
*/
|
|
59
|
+
function defineRule(rule) {
|
|
60
|
+
Schema.decodeUnknownSync(RuleMeta)(rule.meta);
|
|
61
|
+
return rule;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/domain/config.ts
|
|
65
|
+
/**
|
|
66
|
+
* Configuration types and the `defineConfig` helper.
|
|
67
|
+
*
|
|
68
|
+
* A config file (`agentreview.config.ts`) default-exports an {@link AgentReviewConfig}
|
|
69
|
+
* object that maps rule names to rule definitions and optionally scopes which
|
|
70
|
+
* files are scanned.
|
|
71
|
+
*
|
|
72
|
+
* @module
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Identity function that provides type inference and IDE support for config files.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { defineConfig } from "agentreview"
|
|
81
|
+
*
|
|
82
|
+
* export default defineConfig({
|
|
83
|
+
* include: ["src/**\/*.ts"],
|
|
84
|
+
* rules: { "my-rule": myRule }
|
|
85
|
+
* })
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
* @category constructors
|
|
90
|
+
*/
|
|
91
|
+
function defineConfig(config) {
|
|
92
|
+
for (const rule of Object.values(config.rules)) Schema.decodeUnknownSync(RuleMeta)(rule.meta);
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { FlagRecord, Position, RuleMeta, defineConfig, defineRule };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/domain/rule.ts","../src/domain/config.ts"],"sourcesContent":["/**\n * Rule definition types and the `defineRule` helper.\n *\n * A rule is a reusable lint check defined by {@link RuleMeta} (what to check)\n * and a `createOnce` factory (how to check it). The factory returns a\n * {@link Visitors} object whose keys are tree-sitter node types.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\nimport type { AgentReviewNode } from \"./node.js\";\nimport type { TreeSitterNodeType } from \"./node-types.js\";\nimport type { RuleContext } from \"./rule-context.js\";\n\n/**\n * Static metadata for a rule.\n *\n * Defined as a `Schema.Struct` so that rule metadata is validated at\n * runtime when `defineRule` is called — catches typos, missing fields,\n * and wrong types with clear error messages.\n *\n * @since 0.1.0\n * @category models\n */\nexport const RuleMeta = Schema.Struct({\n /** Unique identifier. kebab-case. Used in output, --rule filtering, agentreview-ignore. */\n name: Schema.String,\n /** One-liner explaining what the rule checks. */\n description: Schema.String,\n /** File extensions this rule applies to, without the dot. e.g. `[\"ts\", \"tsx\"]` */\n languages: Schema.Array(Schema.String),\n /**\n * Natural language instruction for the calling AI agent.\n * Defines pass/fail criteria and how to evaluate flagged matches.\n */\n instruction: Schema.String,\n /** If provided, rule only runs on files matching these globs (after global filtering). */\n include: Schema.optional(Schema.Array(Schema.String)),\n /** If provided, files matching these globs are excluded from this rule. */\n ignore: Schema.optional(Schema.Array(Schema.String)),\n});\n\n/** @since 0.1.0 */\nexport type RuleMeta = Schema.Schema.Type<typeof RuleMeta>;\n\n/**\n * Callback invoked when a matching AST node type is visited.\n *\n * @since 0.1.0\n * @category models\n */\nexport type VisitorHandler = (node: AgentReviewNode) => void;\n\n/**\n * Visitor object returned by `createOnce`.\n *\n * Maps tree-sitter node type strings to handler functions.\n * Known node types provide autocomplete; any string is accepted.\n *\n * @since 0.1.0\n * @category models\n */\nexport type Visitors = {\n /**\n * Called once before each file is traversed.\n * Return `false` to skip this file entirely for this rule.\n */\n before?: ((filename: string) => boolean | void) | undefined;\n /**\n * Called once after all files have been visited.\n * Use for aggregate analysis.\n */\n after?: (() => void) | undefined;\n} & { [K in TreeSitterNodeType]?: VisitorHandler } & {\n [nodeType: string]: VisitorHandler | ((filename: string) => boolean | void) | (() => void) | undefined;\n};\n\n/**\n * A complete rule definition.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface AgentReviewRule {\n readonly meta: RuleMeta;\n /**\n * Called once per agentreview run (not per file).\n * The returned visitor object is reused across files.\n * Per-file state must be reset in `before()`.\n */\n readonly createOnce: (context: RuleContext) => Visitors;\n}\n\n/**\n * Identity function that provides type inference and IDE support for rule definitions.\n *\n * @example\n * ```ts\n * import { defineRule } from \"agentreview\"\n *\n * export const myRule = defineRule({\n * meta: {\n * name: \"my-rule\",\n * description: \"Checks for something\",\n * languages: [\"ts\", \"tsx\"],\n * instruction: \"Evaluate whether ...\"\n * },\n * createOnce(context) {\n * return {\n * comment(node) {\n * context.flag({ node, message: \"Found comment\" })\n * }\n * }\n * }\n * })\n * ```\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function defineRule(rule: AgentReviewRule): AgentReviewRule {\n Schema.decodeUnknownSync(RuleMeta)(rule.meta);\n return rule;\n}\n","/**\n * Configuration types and the `defineConfig` helper.\n *\n * A config file (`agentreview.config.ts`) default-exports an {@link AgentReviewConfig}\n * object that maps rule names to rule definitions and optionally scopes which\n * files are scanned.\n *\n * @module\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\";\nimport type { AgentReviewRule } from \"./rule.js\";\nimport { RuleMeta } from \"./rule.js\";\n\n/**\n * Top-level configuration schema for `agentreview.config.ts`.\n *\n * @since 0.1.0\n * @category models\n */\nexport interface AgentReviewConfig {\n /** Rule registry. Keys are kebab-case names used in output and `--rule` filtering. */\n readonly rules: Record<string, AgentReviewRule>;\n /** If provided, only files matching at least one pattern are scanned. */\n readonly include?: ReadonlyArray<string> | undefined;\n /** Files matching any pattern are excluded (merged with built-in defaults). */\n readonly ignore?: ReadonlyArray<string> | undefined;\n}\n\n/**\n * Identity function that provides type inference and IDE support for config files.\n *\n * @example\n * ```ts\n * import { defineConfig } from \"agentreview\"\n *\n * export default defineConfig({\n * include: [\"src/**\\/*.ts\"],\n * rules: { \"my-rule\": myRule }\n * })\n * ```\n *\n * @since 0.1.0\n * @category constructors\n */\nexport function defineConfig(config: AgentReviewConfig): AgentReviewConfig {\n for (const rule of Object.values(config.rules)) {\n Schema.decodeUnknownSync(RuleMeta)(rule.meta);\n }\n return config;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,WAAW,OAAO,OAAO;CAEpC,MAAM,OAAO;CAEb,aAAa,OAAO;CAEpB,WAAW,OAAO,MAAM,OAAO,OAAO;CAKtC,aAAa,OAAO;CAEpB,SAAS,OAAO,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC;CAErD,QAAQ,OAAO,SAAS,OAAO,MAAM,OAAO,OAAO,CAAC;CACrD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFF,SAAgB,WAAW,MAAwC;AACjE,QAAO,kBAAkB,SAAS,CAAC,KAAK,KAAK;AAC7C,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9ET,SAAgB,aAAa,QAA8C;AACzE,MAAK,MAAM,QAAQ,OAAO,OAAO,OAAO,MAAM,CAC5C,QAAO,kBAAkB,SAAS,CAAC,KAAK,KAAK;AAE/C,QAAO"}
|