Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,4 @@ docs/mastra-features-implementation
# Generated docs search index
docs-site/static/search-index.json
.claude/worktrees/
.type-consolidation/
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ These are non-negotiable. Violating them breaks the build or introduces bugs.

13. **Barrel-only imports for internal types** — Code outside `src/lib/types/` must import internal types from the barrel (`../types/index.js` or `../types`), never from specific type files (`../types/rag.js`, `../types/mcp.js`). External library types (`zod`, `@ai-sdk/provider`, etc.) can be imported normally. Files inside `src/lib/types/` are exempt (they import from each other).

**Enforcement:** All rules (7-13) are enforced by custom ESLint rules in `eslint-rules/`. Run `pnpm run lint` (or the pre-commit hook) — no shell scripts, no regex heuristics, everything AST-based.
**Enforcement:** All rules (2, 7-13) are enforced by custom ESLint rules in `eslint-rules/`. Run `pnpm run lint` (or the pre-commit hook) — no shell scripts, no regex heuristics, everything AST-based.

| Rule | ESLint rule |
| -------- | ---------------------------------------- |
| 2 | `neurolink/no-local-type-alias` |
| 7 | `neurolink/no-interface` |
| 8 | `neurolink/no-types-suffix-filename` |
| 9 | `neurolink/unique-type-names` |
Expand Down
4 changes: 4 additions & 0 deletions eslint-rules/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
* neurolink/no-local-types-folder → Rules 11 & 11b: Types must live in src/lib/types/.
* neurolink/no-type-export-outside-types → Rule 12: No `export type` outside src/lib/types/.
* neurolink/barrel-type-imports → Rule 13: Internal type imports must use the barrel.
* neurolink/no-local-type-alias → Rule 2 (strict): No `type X = ...` alias outside
* src/lib/types/ (catches non-exported aliases
* that the Rule 12 rule misses).
*/

"use strict";
Expand All @@ -27,5 +30,6 @@ module.exports = {
"no-local-types-folder": require("./no-local-types-folder.cjs"),
"no-type-export-outside-types": require("./no-type-export-outside-types.cjs"),
"barrel-type-imports": require("./barrel-type-imports.cjs"),
"no-local-type-alias": require("./no-local-type-alias.cjs"),
},
};
87 changes: 87 additions & 0 deletions eslint-rules/no-local-type-alias.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Rule 2 enforcement: No type-alias declarations outside src/lib/types/.
*
* Catches every form of local type-alias declaration that lives outside the
* canonical types folder, whether or not the alias is exported:
*
* type X = { ... }; // local alias (new enforcement)
* export type X = { ... }; // exported alias (also caught by
* // Rule 12 — this rule is a safety net)
* type X<T> = Foo<T>; // generic alias
*
* Exempt paths:
* - src/lib/types/** (canonical types folder)
* - src/test/**, **\/*.test.ts (test fixtures may declare throwaway aliases)
* - eslint-rules/** (AST-manipulation rules genuinely need local types)
*
* Special allowances:
* - None. Every violation should be fixed by moving the type into
* src/lib/types/ and importing it back through the barrel. If a genuine
* exception is needed (e.g. a type derived from a local runtime value
* that cannot be materialised), add an explicit eslint-disable-next-line
* with a comment justifying the exception.
*/

"use strict";

/** Returns true if the file is inside src/lib/types/ (canonical types folder). */
function isInsideTypesFolder(filename) {
const normalized = filename.replace(/\\/g, "/");
return /\/src\/lib\/types\//.test(normalized);
}

/** Returns true if the file is a test fixture or spec file. */
function isTestFile(filename) {
const normalized = filename.replace(/\\/g, "/");
return (
/\/src\/test\//.test(normalized) ||
/\.test\.ts$/.test(normalized) ||
/\.spec\.ts$/.test(normalized) ||
/\/test\//.test(normalized)
);
}

/** Returns true if the file is an ESLint rule itself (rule implementation). */
function isEslintRule(filename) {
const normalized = filename.replace(/\\/g, "/");
return /\/eslint-rules\//.test(normalized);
}

/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description:
"Disallow any `type X = ...` alias declaration outside src/lib/types/ (Critical Rule 2).",
},
schema: [],
messages: {
noLocalTypeAlias:
"Type alias `{{name}}` must live in src/lib/types/, not here. Move the declaration into the appropriate barrel file and import it back via the types barrel. See CLAUDE.md Critical Rule 2.",
},
},

create(context) {
const filename = context.filename || context.getFilename();
if (
isInsideTypesFolder(filename) ||
isTestFile(filename) ||
isEslintRule(filename)
) {
return {};
}

return {
TSTypeAliasDeclaration(node) {
context.report({
node,
messageId: "noLocalTypeAlias",
data: {
name: node.id && node.id.name ? node.id.name : "<anonymous>",
},
});
},
};
},
};
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export default [
"neurolink/no-local-types-folder": "error", // Rules 11 & 11b
"neurolink/no-type-export-outside-types": "error", // Rule 12
"neurolink/barrel-type-imports": "error", // Rule 13
"neurolink/no-local-type-alias": "error", // Rule 2 (strict)

// Disable base rules that are covered by TypeScript
"no-unused-vars": "off",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@
"shell-quote": "^1.8.3",
"svelte": "^5.53.6",
"svelte-check": "^4.4.4",
"ts-morph": "^24.0.0",
"tslib": "^2.8.1",
"tsx": "^4.21.0",
"typedoc": "^0.28.17",
Expand Down
30 changes: 30 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/cli/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ import {
OAUTH_BETA_HEADERS,
} from "../../lib/auth/anthropicOAuth.js";
import type {
AccountQuota,
AuthCommandArgs,
StoredCredentials,
AuthStatusResult,
OAuthTokens as OAuthTokensType,
AccountQuota,
StoredCredentials,
SupportedProvider,
} from "../../lib/types/index.js";
import { loadAccountQuotas } from "../../lib/proxy/accountQuota.js";

Expand Down Expand Up @@ -87,7 +88,6 @@ const ANTHROPIC_CONSOLE_OAUTH_CONFIG = {

// Supported providers
const SUPPORTED_PROVIDERS = ["anthropic"] as const;
type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number];

// =============================================================================
// SUBCOMMAND HANDLERS
Expand Down
21 changes: 5 additions & 16 deletions src/cli/commands/autoresearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import chalk from "chalk";
import ora from "ora";
import type { CommandModule } from "yargs";
import type {
AutoresearchInitArgs,
MetricDirection,
ResearchConfig,
ResearchState,
Expand Down Expand Up @@ -86,7 +87,9 @@ export class AutoresearchCommandFactory {
.option("provider", { type: "string" })
.option("model", { type: "string" }),
async (argv) => {
await AutoresearchCommandFactory.executeInit(argv as InitArgs);
await AutoresearchCommandFactory.executeInit(
argv as AutoresearchInitArgs,
);
},
)
.command(
Expand Down Expand Up @@ -269,7 +272,7 @@ export class AutoresearchCommandFactory {
return store;
}

private static async executeInit(argv: InitArgs): Promise<void> {
private static async executeInit(argv: AutoresearchInitArgs): Promise<void> {
const spinner = ora("Initializing autoresearch...").start();
try {
const repoPath = resolve(argv.repoPath);
Expand Down Expand Up @@ -683,17 +686,3 @@ export class AutoresearchCommandFactory {
console.info(chalk.green(`Reset autoresearch state for ${resolved}`));
}
}

type InitArgs = {
repoPath: string;
tag: string;
target: string;
immutable: string;
runCommand: string;
metricName: string;
metricPattern: string;
metricDirection: string;
timeout: number;
provider?: string;
model?: string;
};
21 changes: 12 additions & 9 deletions src/cli/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ import { CLI_LIMITS } from "../../lib/core/constants.js";

import { logger } from "../../lib/utils/logger.js";
import { getTopModelChoices } from "../../lib/utils/modelChoices.js";
import { AIProviderName } from "../../lib/types/index.js";
// Configuration schema for validation
const ConfigSchema = z.object({
import {
AIProviderName,
type CliNeuroLinkConfig,
} from "../../lib/types/index.js";
// Configuration schema for validation. Annotated with
// z.ZodType<CliNeuroLinkConfig> so drift between the canonical structural
// type in src/lib/types/cli.ts and this runtime schema fails at compile time.
const ConfigSchema: z.ZodType<CliNeuroLinkConfig> = z.object({
defaultProvider: z
.enum([
"auto",
Expand Down Expand Up @@ -312,12 +317,10 @@ const ConfigSchema = z.object({
}),
});

type NeuroLinkConfig = z.infer<typeof ConfigSchema>;

export class ConfigManager {
private configDir: string;
private configFile: string;
private config: NeuroLinkConfig;
private config: CliNeuroLinkConfig;

constructor() {
this.configDir = path.join(os.homedir(), ".neurolink");
Expand All @@ -328,7 +331,7 @@ export class ConfigManager {
/**
* Load configuration from file or create default
*/
private loadConfig(): NeuroLinkConfig {
private loadConfig(): CliNeuroLinkConfig {
try {
if (fs.existsSync(this.configFile)) {
const configData = JSON.parse(fs.readFileSync(this.configFile, "utf8"));
Expand Down Expand Up @@ -926,14 +929,14 @@ export class ConfigManager {
/**
* Get current configuration
*/
getConfig(): NeuroLinkConfig {
getConfig(): CliNeuroLinkConfig {
return this.config;
}

/**
* Update configuration
*/
updateConfig(updates: Partial<NeuroLinkConfig>): void {
updateConfig(updates: Partial<CliNeuroLinkConfig>): void {
this.config = { ...this.config, ...updates };
this.saveConfig();
}
Expand Down
Loading
Loading