@jterrazz/intelligence 4.2.0 → 6.0.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/README.md +233 -217
- package/dist/formatting.cjs +2845 -0
- package/dist/formatting.cjs.map +1 -0
- package/dist/formatting.d.cts +105 -0
- package/dist/formatting.d.ts +105 -0
- package/dist/formatting.js +2819 -0
- package/dist/formatting.js.map +1 -0
- package/dist/index.cjs +683 -476
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -1396
- package/dist/index.d.ts +183 -1396
- package/dist/index.js +680 -459
- package/dist/index.js.map +1 -1
- package/dist/oxlint.cjs +629 -0
- package/dist/oxlint.cjs.map +1 -0
- package/dist/oxlint.d.cts +132 -0
- package/dist/oxlint.d.ts +132 -0
- package/dist/oxlint.js +623 -0
- package/dist/oxlint.js.map +1 -0
- package/package.json +28 -22
- package/dist/parse-text.cjs +0 -57
- package/dist/parse-text.cjs.map +0 -1
- package/dist/parse-text.d.cts +0 -18
- package/dist/parse-text.d.ts +0 -18
- package/dist/parse-text.js +0 -52
- package/dist/parse-text.js.map +0 -1
- package/dist/text.cjs +0 -3
- package/dist/text.d.cts +0 -2
- package/dist/text.d.ts +0 -2
- package/dist/text.js +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oxlint.cjs","names":[],"sources":["../src/lint/ast.ts","../src/lint/agents.ts","../src/lint/manifest.ts","../src/lint/rules/g1-agent-class-shape.ts","../src/lint/rules/m1-model-resolution-in-container.ts","../src/lint/rules/m2w-no-hardcoded-model-id.ts","../src/lint/rules/p1-prose-in-prompt-files.ts","../src/lint/rules/p2-prompt-file-exports.ts","../src/lint/fs.ts","../src/lint/rules/p3-agent-prompt-sibling.ts","../src/lint/plugin.ts"],"sourcesContent":["import type { AstNode } from './types.js';\n\n/**\n * Shared AST helpers for the rule files. Everything here is pure and\n * structural: rules narrow nodes by `type` and read fields defensively, so the\n * layer stays decoupled from oxlint's internal (alpha) typings (mirrors\n * `@jterrazz/test`'s `src/lint/ast.ts`).\n */\n\n/** Split a path into its non-empty segments (posix or win separators). */\nexport function segments(path: string): string[] {\n return path.split(/[/\\\\]/).filter(Boolean);\n}\n\n/** The string value of a plain string literal (or a template with no holes). */\nexport function stringValue(node: AstNode | undefined): string | undefined {\n if (node === undefined) {\n return undefined;\n }\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n if (node.type === 'TemplateLiteral') {\n const expressions = node.expressions as AstNode[] | undefined;\n const quasis = node.quasis as AstNode[] | undefined;\n if (expressions?.length === 0 && quasis?.length === 1) {\n return (quasis[0].value as undefined | { cooked?: string })?.cooked;\n }\n }\n return undefined;\n}\n\n/** The property name of a non-computed member expression, if identifiable. */\nexport function memberPropertyName(node: AstNode): string | undefined {\n if (node.type !== 'MemberExpression' || node.computed === true) {\n return undefined;\n }\n const property = node.property as AstNode | undefined;\n return property?.type === 'Identifier' ? (property.name as string) : undefined;\n}\n","import { dirname } from 'node:path';\n\nimport { segments } from './ast.js';\n\n/**\n * Shared \"is this file an agent file / a prompt file?\" detection for\n * P1/P3/G1 — the folder-per-agent convention documented in the README's\n * \"Agent & prompt conventions\" section:\n *\n * ```\n * agents/<name>/<name>.ts # the agent file\n * agents/<name>/<name>.prompt.ts # the prompt file\n * agents/_shared/<name>.prompt.ts # shared prompt sections (P3-exempt)\n * ```\n */\n\n/** An **agent file**: `<name>.ts` in a `<name>/` folder under an `agents/` ancestor. */\nexport type AgentFile = { name: string };\n\n/** A **prompt file**: `*.prompt.ts`, its directory, and whether it's under `_shared/`. */\nexport type PromptFile = { dir: string; name: string; shared: boolean };\n\n/**\n * Is `filePath` under a folder literally named `agents` (exact path segment,\n * anywhere in the path — P1's broader scope, unlike {@link detectAgentFile}'s\n * narrower `<name>/<name>.ts` shape)?\n */\nexport function isUnderAgentsFolder(filePath: string): boolean {\n return segments(filePath).includes('agents');\n}\n\n/**\n * `agents/<name>/<name>.ts` — the file basename (minus `.ts`) must equal its\n * parent directory name, and an `agents` segment must be a proper ancestor of\n * that parent directory. Returns `undefined` for anything else, including\n * `*.prompt.ts` and `*.test.ts` (never agent files).\n */\nexport function detectAgentFile(filePath: string): AgentFile | undefined {\n const parts = segments(filePath);\n const base = parts.at(-1);\n if (base === undefined || !base.endsWith('.ts')) {\n return undefined;\n }\n if (base.endsWith('.prompt.ts') || base.endsWith('.test.ts')) {\n return undefined;\n }\n const stem = base.slice(0, -'.ts'.length);\n const parentIndex = parts.length - 2;\n if (parts[parentIndex] !== stem) {\n return undefined;\n }\n const agentsIndex = parts.lastIndexOf('agents');\n if (agentsIndex === -1 || agentsIndex >= parentIndex) {\n return undefined;\n }\n return { name: stem };\n}\n\n/** `<name>.prompt.ts` — any directory; `shared` is true directly under `_shared/`. */\nexport function detectPromptFile(filePath: string): PromptFile | undefined {\n const parts = segments(filePath);\n const base = parts.at(-1);\n if (base === undefined || !base.endsWith('.prompt.ts')) {\n return undefined;\n }\n const stem = base.slice(0, -'.prompt.ts'.length);\n return {\n dir: dirname(filePath),\n name: stem,\n shared: parts.at(-2) === '_shared',\n };\n}\n","import type { RuleDoc } from './types.js';\n\n/**\n * The rule manifest — the single source of truth for the mechanized\n * agent/prompt conventions catalogue (mirrors `@jterrazz/test`'s\n * `src/lint/manifest.ts` docs-as-code inversion: the normative text lives\n * NEXT TO the rule it documents, attached as `meta.docs`, instead of drifting\n * apart in a hand-maintained doc).\n *\n * The README's \"Agent & prompt conventions\" section is the human-facing\n * explanation of *why* this shape exists; this manifest is the machine-facing\n * \"what exactly is checked\" — `plugin.test.ts` asserts every shipped rule\n * carries its entry and that no entry is orphaned.\n */\nexport const RULE_DOCS: Record<string, RuleDoc> = {\n 'g1-agent-class-shape': {\n id: 'G1',\n convention:\n 'An agent file (`agents/<name>/<name>.ts`) exports exactly one class; that class has a `static readonly SCHEMA` member, a `run` method, and a constructor whose first parameter is named `model`.',\n rationale:\n 'A fixed shape makes every agent class predictable to read and to wire up from a DI container without re-deriving its contract each time.',\n },\n 'm1-model-resolution-in-container': {\n id: 'M1',\n convention:\n \"Calls to `createIntelligence(...)`, `createGatewayProvider(...)`, `createOpenRouterProvider(...)`, and `.model('…')` on the value they produce, are only allowed in a file whose path contains `/di/` or whose name ends in `container.ts`.\",\n rationale:\n 'Model resolution is composition-root work — scattering it lets a provider/model choice drift outside the one place meant to own it.',\n },\n 'm2w-no-hardcoded-model-id': {\n id: 'M2',\n convention:\n 'A string literal that looks like a model id (`claude-…`, `openai/gpt-…`, …) outside config/test/fixture/spec files is a warning — model ids belong in configuration.',\n rationale:\n 'A model id inlined in application code can only change by a code deploy; configuration lets it change without one.',\n },\n 'p1-prose-in-prompt-files': {\n id: 'P1',\n convention:\n 'In any file under an `agents/` folder that is not a `*.prompt.ts` (nor a `*.test.ts`), a template literal spanning 3+ lines that reads as natural-language prose (a line with 4+ space-separated words, or a markdown heading) is an error — move it to the sibling `*.prompt.ts`.',\n rationale:\n 'The agent class only shapes data; prose that leaks into it hides the actual prompt contract and makes the two impossible to review independently.',\n },\n 'p2-prompt-file-exports': {\n id: 'P2',\n convention:\n 'A `*.prompt.ts` file exports only const arrow functions returning a string, plus types/interfaces — no default export, no class, no non-function const.',\n rationale:\n 'A closed export surface keeps a prompt file a pure builder module — anything else (state, a class, a default export) would invite prompt logic to grow side effects.',\n },\n 'p3-agent-prompt-sibling': {\n id: 'P3',\n convention:\n 'An agent file (`agents/<name>/<name>.ts`) imports its prompt from `./<name>.prompt.js`; a `<name>.prompt.ts` file outside `_shared/` has a sibling `<name>.ts` in the same directory.',\n rationale:\n \"The two-way link is what makes the pairing mechanical instead of a naming convention nobody enforces — an orphaned prompt file, or an agent that silently doesn't use its prompt, is almost always a mistake.\",\n },\n};\n","import { detectAgentFile } from '../agents.js';\nimport { RULE_DOCS } from '../manifest.js';\nimport type { AstNode, LintRule, RuleContext, Visitor } from '../types.js';\n\n/** Unwrap `private readonly model: T` (TSParameterProperty) / defaults to the bare identifier. */\nfunction parameterName(param: AstNode | undefined): string | undefined {\n if (param === undefined) {\n return undefined;\n }\n if (param.type === 'Identifier') {\n return param.name as string;\n }\n if (param.type === 'TSParameterProperty') {\n return parameterName(param.parameter as AstNode | undefined);\n }\n if (param.type === 'AssignmentPattern') {\n return parameterName(param.left as AstNode | undefined);\n }\n return undefined;\n}\n\nfunction classBody(classNode: AstNode): AstNode[] {\n const body = classNode.body as AstNode | undefined;\n return (body?.body as AstNode[] | undefined) ?? [];\n}\n\n/**\n * CONVENTIONS G1 — an agent file's class shape: exactly one exported class,\n * carrying a `static readonly SCHEMA` member, a `run` method, and a\n * constructor whose first parameter is `model` (the `LanguageModel` the\n * class's `run()` passes straight to `generateText`/`streamText`).\n */\nexport const g1AgentClassShape: LintRule = {\n create(context: RuleContext): Visitor {\n const file = context.physicalFilename;\n if (detectAgentFile(file) === undefined) {\n return {};\n }\n\n const exportedClasses: AstNode[] = [];\n\n return {\n ExportDefaultDeclaration(node: AstNode) {\n const declaration = node.declaration as AstNode | undefined;\n if (\n declaration?.type === 'ClassDeclaration' ||\n declaration?.type === 'ClassExpression'\n ) {\n exportedClasses.push(declaration);\n }\n },\n ExportNamedDeclaration(node: AstNode) {\n const declaration = node.declaration as AstNode | undefined;\n if (declaration?.type === 'ClassDeclaration') {\n exportedClasses.push(declaration);\n }\n },\n 'Program:exit'(node: AstNode) {\n if (exportedClasses.length === 0) {\n context.report({ messageId: 'noExportedClass', node });\n return;\n }\n if (exportedClasses.length > 1) {\n for (const extra of exportedClasses.slice(1)) {\n context.report({ messageId: 'multipleExportedClasses', node: extra });\n }\n }\n\n const target = exportedClasses[0];\n const members = classBody(target);\n\n const hasSchema = members.some((member) => {\n if (member.type !== 'PropertyDefinition' || member.static !== true) {\n return false;\n }\n const key = member.key as AstNode | undefined;\n return key?.type === 'Identifier' && key.name === 'SCHEMA';\n });\n if (!hasSchema) {\n context.report({ messageId: 'missingSchema', node: target });\n }\n\n const hasRun = members.some((member) => {\n if (\n member.type !== 'MethodDefinition' &&\n member.type !== 'PropertyDefinition'\n ) {\n return false;\n }\n const key = member.key as AstNode | undefined;\n return key?.type === 'Identifier' && key.name === 'run';\n });\n if (!hasRun) {\n context.report({ messageId: 'missingRun', node: target });\n }\n\n const constructor = members.find(\n (member) =>\n member.type === 'MethodDefinition' &&\n (member.kind === 'constructor' ||\n (member.key as AstNode | undefined)?.name === 'constructor'),\n );\n if (constructor === undefined) {\n context.report({ messageId: 'missingConstructorModel', node: target });\n return;\n }\n const value = constructor.value as AstNode | undefined;\n const firstParam = (value?.params as AstNode[] | undefined)?.[0];\n if (parameterName(firstParam) !== 'model') {\n context.report({\n messageId: 'missingConstructorModel',\n node: firstParam ?? constructor,\n });\n }\n },\n };\n },\n meta: {\n docs: RULE_DOCS['g1-agent-class-shape'],\n messages: {\n missingConstructorModel:\n \"Agent class constructor's first parameter must be named `model` (G1).\",\n missingRun: 'Agent class is missing a `run` method (G1).',\n missingSchema: 'Agent class is missing a `static readonly SCHEMA` member (G1).',\n multipleExportedClasses: 'Agent file exports more than one class — exactly one (G1).',\n noExportedClass: 'Agent file exports no class — exactly one is required (G1).',\n },\n type: 'problem',\n },\n};\n","import { memberPropertyName, stringValue } from '../ast.js';\nimport { RULE_DOCS } from '../manifest.js';\nimport type { AstNode, LintRule, RuleContext, Visitor } from '../types.js';\n\n/** The composition-root factories `@jterrazz/intelligence` exposes. */\nconst FACTORY_NAMES = new Set([\n 'createGatewayProvider',\n 'createIntelligence',\n 'createOpenRouterProvider',\n]);\n\n/** Is `file` a DI/composition-root file? */\nfunction isContainerFile(file: string): boolean {\n return file.includes('/di/') || file.endsWith('container.ts');\n}\n\n/**\n * CONVENTIONS M1 — model resolution is composition-root work. `createIntelligence`\n * and the two provider factories, plus `.model('…')` calls on whatever they\n * produce, are only allowed in a file under `di/` or named `*container.ts`.\n *\n * Detection is best effort by design: a `.model(<string literal>)` call is\n * flagged wherever it appears in a file that imports `@jterrazz/intelligence`,\n * WITHOUT verifying the receiver is actually the `Intelligence` instance —\n * static analysis cannot reliably trace that binding across parameter\n * passing/destructuring (see the container.ts example in the README, where\n * `Intelligence` arrives as an injected parameter, not a local `const`). A\n * project with an unrelated `.model()` method on some other object, imported\n * from the same file as `@jterrazz/intelligence`, would false-positive here —\n * an accepted, documented limitation of this rule.\n */\nexport const m1ModelResolutionInContainer: LintRule = {\n create(context: RuleContext): Visitor {\n const file = context.physicalFilename;\n const allowed = isContainerFile(file);\n\n let importsIntelligence = false;\n const modelCalls: AstNode[] = [];\n const factoryCalls: { name: string; node: AstNode }[] = [];\n\n return {\n CallExpression(node: AstNode) {\n const callee = node.callee as AstNode | undefined;\n if (callee === undefined) {\n return;\n }\n if (callee.type === 'Identifier' && FACTORY_NAMES.has(callee.name as string)) {\n factoryCalls.push({ name: callee.name as string, node });\n return;\n }\n if (callee.type === 'MemberExpression' && memberPropertyName(callee) === 'model') {\n const args = (node.arguments as AstNode[] | undefined) ?? [];\n if (args.length === 1 && stringValue(args[0]) !== undefined) {\n modelCalls.push(node);\n }\n }\n },\n ImportDeclaration(node: AstNode) {\n if (stringValue(node.source as AstNode | undefined) === '@jterrazz/intelligence') {\n importsIntelligence = true;\n }\n },\n 'Program:exit'() {\n if (allowed) {\n return;\n }\n for (const { name, node } of factoryCalls) {\n context.report({ data: { name }, messageId: 'factoryOutsideContainer', node });\n }\n if (importsIntelligence) {\n for (const node of modelCalls) {\n context.report({ messageId: 'modelCallOutsideContainer', node });\n }\n }\n },\n };\n },\n meta: {\n docs: RULE_DOCS['m1-model-resolution-in-container'],\n messages: {\n factoryOutsideContainer:\n '{{name}}() must only be called from a DI/container file (path containing \"/di/\" or ending in \"container.ts\") (M1).',\n modelCallOutsideContainer:\n \".model('…') resolution must only happen in a DI/container file (M1).\",\n },\n type: 'problem',\n },\n};\n","import { segments } from '../ast.js';\nimport { RULE_DOCS } from '../manifest.js';\nimport type { AstNode, LintRule, RuleContext, Visitor } from '../types.js';\n\n/** `claude-…`, `gpt-4o`, `o1-preview`, `grok-2`, `deepseek-v3`, … */\nconst BARE_MODEL_ID =\n /^(?<family>claude|deepseek|gemini|gpt|grok|llama|mistral|o[0-9])[-0-9a-z.]/iu;\n/** `openai/gpt-4o`, `anthropic/claude-3-5-sonnet`, … (provider-prefixed form). */\nconst PREFIXED_MODEL_ID = /^[a-z0-9-]+\\/(?<family>claude|deepseek|gemini|gpt|grok|llama|mistral)/iu;\n\n/** Config/test/fixture/spec files are exempt — that's exactly where a model id belongs. */\nfunction isExemptFile(file: string): boolean {\n const parts = segments(file);\n const base = parts.at(-1) ?? '';\n if (/\\.(?:test|spec)\\.[cm]?tsx?$/u.test(base)) {\n return true;\n }\n if (parts.includes('fixtures') || parts.includes('specs') || parts.includes('__fixtures__')) {\n return true;\n }\n return parts.includes('config') || /\\.config\\.[cm]?tsx?$/u.test(base);\n}\n\nfunction looksLikeModelId(value: string): boolean {\n return BARE_MODEL_ID.test(value) || PREFIXED_MODEL_ID.test(value);\n}\n\n/**\n * CONVENTIONS M2 (warning) — a string literal shaped like a model id belongs\n * in configuration, not inlined in application code. Config/test/fixture/spec\n * files are exempt by design (oxlint only ever visits `.ts`/`.tsx` sources —\n * a `.yml`/`.json` config value is never in its reach regardless).\n */\nexport const m2wNoHardcodedModelId: LintRule = {\n create(context: RuleContext): Visitor {\n const file = context.physicalFilename;\n if (isExemptFile(file)) {\n return {};\n }\n\n return {\n Literal(node: AstNode) {\n if (typeof node.value !== 'string') {\n return;\n }\n if (looksLikeModelId(node.value)) {\n context.report({\n data: { value: node.value },\n messageId: 'hardcodedModelId',\n node,\n });\n }\n },\n };\n },\n meta: {\n docs: RULE_DOCS['m2w-no-hardcoded-model-id'],\n messages: {\n hardcodedModelId:\n 'String \"{{value}}\" looks like a model id — model ids belong in configuration (M2).',\n },\n type: 'suggestion',\n },\n};\n","import { isUnderAgentsFolder } from '../agents.js';\nimport { RULE_DOCS } from '../manifest.js';\nimport type { AstNode, LintRule, RuleContext, Visitor } from '../types.js';\n\n/** A markdown heading — `#`, `##`, or `###` followed by a space. */\nconst MARKDOWN_HEADING = /^#{1,3}\\s/;\n/** A line reading as prose: 4+ tokens separated by whitespace. */\nconst MIN_PROSE_WORDS = 4;\n\n/** Does `text` (one physical line) look like natural-language prose? */\nfunction looksLikeProseLine(line: string): boolean {\n const trimmed = line.trim();\n if (trimmed.length === 0) {\n return false;\n }\n if (MARKDOWN_HEADING.test(trimmed)) {\n return true;\n }\n const words = trimmed.split(/\\s+/u).filter(Boolean);\n return words.length >= MIN_PROSE_WORDS;\n}\n\n/** Number of physical lines a source span covers, from a `\\n` count. */\nfunction lineSpan(text: string): number {\n return text.split('\\n').length;\n}\n\n/**\n * CONVENTIONS P1 — the flagship rule: no multi-line natural-language literal\n * outside a `*.prompt.ts` file. A template literal is flagged when its full\n * source span reaches 3+ lines AND at least one of its own static quasis\n * (never the interpolated expressions) reads as prose — a line with 4+\n * space-separated words, or a markdown heading. Single-line literals, JSON-ish\n * multi-line literals, and pure data interpolation stay under the threshold\n * and pass.\n */\nexport const p1ProseInPromptFiles: LintRule = {\n create(context: RuleContext): Visitor {\n const file = context.physicalFilename;\n if (!isUnderAgentsFolder(file)) {\n return {};\n }\n const base = file.split(/[/\\\\]/).pop() ?? '';\n if (base.endsWith('.prompt.ts') || base.endsWith('.test.ts')) {\n return {};\n }\n const target = `${base.replace(/\\.ts$/, '')}.prompt.ts`;\n\n return {\n TemplateLiteral(node: AstNode) {\n const start = (node.start as number | undefined) ?? (node.range as number[])?.[0];\n const end = (node.end as number | undefined) ?? (node.range as number[])?.[1];\n if (typeof start !== 'number' || typeof end !== 'number') {\n return;\n }\n const fullText = context.sourceCode.text.slice(start, end);\n if (lineSpan(fullText) < 3) {\n return;\n }\n const quasis = (node.quasis as AstNode[] | undefined) ?? [];\n const hasProse = quasis.some((quasi) => {\n const raw = (quasi.value as undefined | { raw?: string })?.raw ?? '';\n return raw.split('\\n').some(looksLikeProseLine);\n });\n if (hasProse) {\n context.report({ data: { target }, messageId: 'moveProse', node });\n }\n },\n };\n },\n meta: {\n docs: RULE_DOCS['p1-prose-in-prompt-files'],\n messages: {\n moveProse:\n 'Multi-line natural-language template literal outside a *.prompt.ts file — move prompt prose to {{target}} (P1).',\n },\n type: 'problem',\n },\n};\n","import { RULE_DOCS } from '../manifest.js';\nimport type { AstNode, LintRule, RuleContext, Visitor } from '../types.js';\n\n/** Declaration types that are types, not values — always allowed. */\nconst TYPE_DECLARATIONS = new Set(['TSInterfaceDeclaration', 'TSTypeAliasDeclaration']);\n\n/**\n * Best-effort \"does this expression plausibly evaluate to a string?\" check.\n * Permissive by design (P2 is a shape gate, not a type checker): a delegated\n * call (`return sharedSection();`), a bare identifier, member access, string\n * concatenation, and `? :` / `||` narrowing all pass. Only expressions that\n * are CLEARLY the wrong shape — an object/array literal, a non-string literal,\n * a nested function — are rejected.\n */\nfunction looksLikeStringExpression(node: AstNode | undefined): boolean {\n if (node === undefined) {\n return false;\n }\n switch (node.type) {\n case 'BinaryExpression': {\n return node.operator === '+';\n }\n case 'CallExpression':\n case 'Identifier':\n case 'MemberExpression':\n case 'TemplateLiteral': {\n return true;\n }\n case 'ConditionalExpression': {\n return (\n looksLikeStringExpression(node.consequent as AstNode | undefined) &&\n looksLikeStringExpression(node.alternate as AstNode | undefined)\n );\n }\n case 'Literal': {\n return typeof node.value === 'string';\n }\n case 'LogicalExpression': {\n return looksLikeStringExpression(node.right as AstNode | undefined);\n }\n default: {\n return false;\n }\n }\n}\n\n/**\n * The expressions `fn` itself returns — the concise-arrow expression body, or\n * every top-level `return`'s argument in a block body (never descending into a\n * nested function's own returns). Each is a REAL AST node (unlike a synthetic\n * `ReturnStatement` wrapper), so it always carries a valid position to report on.\n */\nfunction ownReturnExpressions(fn: AstNode): AstNode[] {\n const body = fn.body as AstNode | undefined;\n if (body === undefined) {\n return [];\n }\n if (body.type !== 'BlockStatement') {\n // Concise arrow body: `() => expr` — the body IS the returned expression.\n return [body];\n }\n const expressions: AstNode[] = [];\n const visit = (node: AstNode | undefined): void => {\n if (node === undefined) {\n return;\n }\n if (node.type === 'ReturnStatement') {\n const argument = node.argument as AstNode | undefined;\n if (argument !== undefined) {\n expressions.push(argument);\n }\n return;\n }\n if (\n node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionExpression' ||\n node.type === 'FunctionDeclaration'\n ) {\n return; // Do not descend into a nested function's own returns.\n }\n for (const key of Object.keys(node)) {\n if (key === 'parent') {\n continue;\n }\n const value = node[key];\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isNode(item)) {\n visit(item);\n }\n }\n } else if (isNode(value)) {\n visit(value);\n }\n }\n };\n visit(body);\n return expressions;\n}\n\nfunction isNode(value: unknown): value is AstNode {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { type?: unknown }).type === 'string'\n );\n}\n\n/**\n * CONVENTIONS P2 — a `*.prompt.ts` file's export surface is closed: const\n * arrow functions returning a string (the builders), and types/interfaces.\n * No default export, no class, no non-function const, no plain `function`\n * declaration (the convention is arrow consts specifically — see the\n * README's \"Agent & prompt conventions\" section).\n */\nexport const p2PromptFileExports: LintRule = {\n create(context: RuleContext): Visitor {\n const file = context.physicalFilename;\n if (!file.endsWith('.prompt.ts')) {\n return {};\n }\n\n return {\n Program(node: AstNode) {\n for (const statement of (node.body as AstNode[] | undefined) ?? []) {\n if (statement.type === 'ExportDefaultDeclaration') {\n context.report({ messageId: 'defaultExport', node: statement });\n continue;\n }\n if (statement.type !== 'ExportNamedDeclaration') {\n continue;\n }\n const declaration = statement.declaration as AstNode | undefined;\n if (declaration === undefined) {\n continue; // `export { x };` re-export form — out of P2's static reach.\n }\n if (TYPE_DECLARATIONS.has(declaration.type)) {\n continue;\n }\n if (declaration.type === 'ClassDeclaration') {\n context.report({ messageId: 'classExport', node: statement });\n continue;\n }\n if (declaration.type !== 'VariableDeclaration') {\n // FunctionDeclaration, TSEnumDeclaration, etc. — not the arrow-const shape.\n const declId = declaration.id as AstNode | undefined;\n const declName =\n declId?.type === 'Identifier' ? (declId.name as string) : '?';\n context.report({\n data: { name: declName },\n messageId: 'nonFunctionExport',\n node: statement,\n });\n continue;\n }\n for (const declarator of (declaration.declarations as AstNode[] | undefined) ??\n []) {\n const id = declarator.id as AstNode | undefined;\n const name = id?.type === 'Identifier' ? (id.name as string) : '?';\n const init = declarator.init as AstNode | undefined;\n if (init?.type !== 'ArrowFunctionExpression') {\n context.report({\n data: { name },\n messageId: 'nonFunctionExport',\n node: declarator,\n });\n continue;\n }\n const returnExpressions = ownReturnExpressions(init);\n // A block body with no `return` at all returns `undefined` —\n // Report on the arrow function itself (a real node either way).\n const badReturn =\n returnExpressions.length === 0\n ? init\n : returnExpressions.find(\n (expression) => !looksLikeStringExpression(expression),\n );\n if (badReturn !== undefined) {\n context.report({\n data: { name },\n messageId: 'nonStringReturn',\n node: badReturn,\n });\n }\n }\n }\n },\n };\n },\n meta: {\n docs: RULE_DOCS['p2-prompt-file-exports'],\n messages: {\n classExport:\n 'Prompt file exports a class — a *.prompt.ts file exports only const string-builder functions and types (P2).',\n defaultExport:\n 'Prompt file has a default export — a *.prompt.ts file exports only const string-builder functions and types (P2).',\n nonFunctionExport:\n 'Prompt file export \"{{name}}\" is not a const arrow function — a *.prompt.ts file exports only const string-builder functions and types (P2).',\n nonStringReturn:\n 'Prompt file export \"{{name}}\" does not appear to return a string (P2).',\n },\n type: 'problem',\n },\n};\n","import { existsSync } from 'node:fs';\n\n/**\n * Filesystem probe for the one fs-anchored rule (P3 — the sibling check).\n * Mirrors `@jterrazz/test`'s `src/lint/fs-cache.ts` in spirit, minus the\n * memoization: P3 does at most one `existsSync` per visited file, so a cache\n * would add complexity without a measurable payoff at this plugin's size.\n */\nexport function fileExists(path: string): boolean {\n try {\n return existsSync(path);\n } catch {\n return false;\n }\n}\n","import { join } from 'node:path';\n\nimport { detectAgentFile, detectPromptFile } from '../agents.js';\nimport { stringValue } from '../ast.js';\nimport { fileExists } from '../fs.js';\nimport { RULE_DOCS } from '../manifest.js';\nimport type { AstNode, LintRule, RuleContext, Visitor } from '../types.js';\n\n/**\n * CONVENTIONS P3 — the two-way link between an agent file and its prompt:\n *\n * - an agent file (`agents/<name>/<name>.ts`) imports its prompt from\n * `./<name>.prompt.js` (best effort: checks for the import SOURCE string,\n * not that the imported bindings are actually used);\n * - a `<name>.prompt.ts` file outside `_shared/` has a sibling `<name>.ts`\n * in the same directory (`fs.existsSync` — mirrors `@jterrazz/test`'s\n * `c8-referenced-fixture-exists`, the same on-disk-reference pattern).\n */\nexport const p3AgentPromptSibling: LintRule = {\n create(context: RuleContext): Visitor {\n const file = context.physicalFilename;\n const agent = detectAgentFile(file);\n const prompt = agent === undefined ? detectPromptFile(file) : undefined;\n if (agent === undefined && prompt === undefined) {\n return {};\n }\n\n return {\n Program(node: AstNode) {\n if (agent !== undefined) {\n const expected = `./${agent.name}.prompt.js`;\n const hasImport = ((node.body as AstNode[] | undefined) ?? []).some(\n (statement) =>\n statement.type === 'ImportDeclaration' &&\n stringValue(statement.source as AstNode | undefined) === expected,\n );\n if (!hasImport) {\n context.report({\n data: { expected },\n messageId: 'missingPromptImport',\n node,\n });\n }\n return;\n }\n if (prompt !== undefined && !prompt.shared) {\n const sibling = join(prompt.dir, `${prompt.name}.ts`);\n if (!fileExists(sibling)) {\n context.report({\n data: { name: prompt.name },\n messageId: 'missingAgentSibling',\n node,\n });\n }\n }\n },\n };\n },\n meta: {\n docs: RULE_DOCS['p3-agent-prompt-sibling'],\n messages: {\n missingAgentSibling:\n 'Prompt file \"{{name}}.prompt.ts\" has no sibling agent file \"{{name}}.ts\" in the same directory (P3).',\n missingPromptImport: 'Agent file must import its prompt from \"{{expected}}\" (P3).',\n },\n type: 'problem',\n },\n};\n","import { g1AgentClassShape } from './rules/g1-agent-class-shape.js';\nimport { m1ModelResolutionInContainer } from './rules/m1-model-resolution-in-container.js';\nimport { m2wNoHardcodedModelId } from './rules/m2w-no-hardcoded-model-id.js';\nimport { p1ProseInPromptFiles } from './rules/p1-prose-in-prompt-files.js';\nimport { p2PromptFileExports } from './rules/p2-prompt-file-exports.js';\nimport { p3AgentPromptSibling } from './rules/p3-agent-prompt-sibling.js';\nimport type { LintPlugin } from './types.js';\n\n/**\n * The `@jterrazz/intelligence` oxlint plugin — formalizes the agent/prompt\n * folder convention documented in the README's \"Agent & prompt conventions\"\n * section as statically-checkable rules, mirroring `@jterrazz/test`'s\n * `src/lint/plugin.ts` (same composable-fragment architecture, same\n * `RuleTester` test layer, same manifest/docs-as-code pattern).\n *\n * Registered in a consumer's `oxlint.config.ts` via\n * `jsPlugins: ['@jterrazz/intelligence/oxlint']` and referenced as\n * `intelligence/<rule>` in the `rules` map — or enabled wholesale via the\n * {@link intelligence} composable fragment:\n *\n * import { compose, node } from '@jterrazz/typescript/oxlint';\n * import { intelligence } from '@jterrazz/intelligence/oxlint';\n * export default compose(node, intelligence);\n *\n * Bundled by tsdown (`dist/oxlint.js`); rules import nothing from this\n * package's AI SDK runtime (only pure structural helpers: `ast.ts`, `fs.ts`,\n * `agents.ts`), so the bundle stays free of the `ai`/`@ai-sdk/*` dependency\n * graph the main entry pulls in.\n */\nconst plugin: LintPlugin = {\n meta: { name: 'intelligence' },\n rules: {\n 'g1-agent-class-shape': g1AgentClassShape,\n 'm1-model-resolution-in-container': m1ModelResolutionInContainer,\n 'm2w-no-hardcoded-model-id': m2wNoHardcodedModelId,\n 'p1-prose-in-prompt-files': p1ProseInPromptFiles,\n 'p2-prompt-file-exports': p2PromptFileExports,\n 'p3-agent-prompt-sibling': p3AgentPromptSibling,\n },\n};\n\n/**\n * The full catalogue at its intended severities — spread into an oxlint\n * `rules` map to enable everything in one line:\n *\n * rules: { ...recommendedRules }\n *\n * Hard conventions are errors; `m2w-*` (the model-id heuristic) is a warning.\n */\nexport const recommendedRules: Record<string, 'error' | 'warn'> = Object.fromEntries(\n Object.keys(plugin.rules).map((rule) => [\n `intelligence/${rule}`,\n /^\\w+w-/.test(rule) ? 'warn' : 'error',\n ]),\n);\n\n/**\n * The composable fragment — wire the plugin and enable the whole catalogue.\n * Designed to be composed with a base preset (e.g. `@jterrazz/typescript/oxlint`):\n *\n * import { compose, node } from '@jterrazz/typescript/oxlint';\n * import { intelligence } from '@jterrazz/intelligence/oxlint';\n * export default compose(node, intelligence);\n *\n * `jsPlugins` registers the tool-facing entry, `rules` is {@link recommendedRules}.\n * `overrides` ships empty (no per-glob relaxation is needed today — every rule\n * gates itself by file path/name internally) but is kept on the fragment's\n * shape for parity with `@jterrazz/test`'s `testing` fragment and so a future\n * relaxation has somewhere to go without a breaking shape change.\n */\nexport const intelligence = {\n jsPlugins: ['@jterrazz/intelligence/oxlint'],\n overrides: [],\n rules: recommendedRules,\n};\n\nexport default plugin;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region src/lint/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Local structural types for the slice of oxlint's JS-plugin API this layer uses.
|
|
4
|
+
*
|
|
5
|
+
* oxlint does not publicly export its `Plugin` / `Rule` / `Context` types (only
|
|
6
|
+
* `RuleTester`, from `oxlint/plugins-dev`), so we describe the exact subset we
|
|
7
|
+
* depend on here. Declaring them locally keeps the rules layer importing
|
|
8
|
+
* NOTHING but these ambient shapes and pure helpers from `ast.ts`/`fs.ts` — no
|
|
9
|
+
* dependency on the AI SDK / `ai` runtime this package otherwise ships, so the
|
|
10
|
+
* oxlint bundle stays light. The API is ESLint-compatible, so these shapes
|
|
11
|
+
* mirror ESTree (mirrors `@jterrazz/test`'s `src/lint/types.ts`).
|
|
12
|
+
*/
|
|
13
|
+
/** Minimal AST node. Rules narrow by `type` and read known fields defensively. */
|
|
14
|
+
type AstNode = {
|
|
15
|
+
type: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
};
|
|
18
|
+
/** The slice of `context.sourceCode` the rules read. */
|
|
19
|
+
type SourceCode = {
|
|
20
|
+
text: string;
|
|
21
|
+
};
|
|
22
|
+
/** A diagnostic accepted by `context.report`. */
|
|
23
|
+
type Diagnostic = {
|
|
24
|
+
data?: Record<string, number | string>;
|
|
25
|
+
messageId?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
node?: AstNode;
|
|
28
|
+
};
|
|
29
|
+
/** Rule context passed to `create`. */
|
|
30
|
+
type RuleContext = {
|
|
31
|
+
filename: string;
|
|
32
|
+
id: string;
|
|
33
|
+
/** Configured rule options (`['error', …options]` minus the severity). */
|
|
34
|
+
options: readonly unknown[];
|
|
35
|
+
physicalFilename: string;
|
|
36
|
+
report: (diagnostic: Diagnostic) => void;
|
|
37
|
+
sourceCode: SourceCode;
|
|
38
|
+
};
|
|
39
|
+
/** A visitor: node-type keys → handlers invoked on entry (plus `Program:exit`). */
|
|
40
|
+
type Visitor = Record<string, (node: AstNode) => void>;
|
|
41
|
+
/**
|
|
42
|
+
* The normative documentation a rule carries — the code is the source of truth
|
|
43
|
+
* for the mechanized catalogue (docs-as-code inversion, mirroring
|
|
44
|
+
* `@jterrazz/test`). Every plugin rule sets `meta.docs` to its {@link RuleDoc}
|
|
45
|
+
* entry from `manifest.ts`; `plugin.test.ts` guards that every shipped rule
|
|
46
|
+
* carries one and that the manifest covers exactly the shipped rules.
|
|
47
|
+
*/
|
|
48
|
+
type RuleDoc = {
|
|
49
|
+
/** Convention code, e.g. `'P1'`. */
|
|
50
|
+
id: string;
|
|
51
|
+
/** The normative sentence — what the rule enforces. */
|
|
52
|
+
convention: string;
|
|
53
|
+
/** One line: why the rule exists. */
|
|
54
|
+
rationale: string;
|
|
55
|
+
};
|
|
56
|
+
/** Rule metadata (the subset we set). */
|
|
57
|
+
type RuleMeta = {
|
|
58
|
+
docs?: RuleDoc & {
|
|
59
|
+
description?: string;
|
|
60
|
+
};
|
|
61
|
+
messages?: Record<string, string>;
|
|
62
|
+
/** JSON schema for options — required by oxlint for rules that take options. */
|
|
63
|
+
schema?: false | unknown[];
|
|
64
|
+
type?: 'layout' | 'problem' | 'suggestion';
|
|
65
|
+
};
|
|
66
|
+
/** A lint rule in oxlint's `create` form. */
|
|
67
|
+
type LintRule = {
|
|
68
|
+
create: (context: RuleContext) => Visitor;
|
|
69
|
+
meta?: RuleMeta;
|
|
70
|
+
};
|
|
71
|
+
/** An oxlint JS plugin: a namespace plus its rules. */
|
|
72
|
+
type LintPlugin = {
|
|
73
|
+
meta: {
|
|
74
|
+
name: string;
|
|
75
|
+
};
|
|
76
|
+
rules: Record<string, LintRule>;
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/lint/plugin.d.ts
|
|
80
|
+
/**
|
|
81
|
+
* The `@jterrazz/intelligence` oxlint plugin — formalizes the agent/prompt
|
|
82
|
+
* folder convention documented in the README's "Agent & prompt conventions"
|
|
83
|
+
* section as statically-checkable rules, mirroring `@jterrazz/test`'s
|
|
84
|
+
* `src/lint/plugin.ts` (same composable-fragment architecture, same
|
|
85
|
+
* `RuleTester` test layer, same manifest/docs-as-code pattern).
|
|
86
|
+
*
|
|
87
|
+
* Registered in a consumer's `oxlint.config.ts` via
|
|
88
|
+
* `jsPlugins: ['@jterrazz/intelligence/oxlint']` and referenced as
|
|
89
|
+
* `intelligence/<rule>` in the `rules` map — or enabled wholesale via the
|
|
90
|
+
* {@link intelligence} composable fragment:
|
|
91
|
+
*
|
|
92
|
+
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
93
|
+
* import { intelligence } from '@jterrazz/intelligence/oxlint';
|
|
94
|
+
* export default compose(node, intelligence);
|
|
95
|
+
*
|
|
96
|
+
* Bundled by tsdown (`dist/oxlint.js`); rules import nothing from this
|
|
97
|
+
* package's AI SDK runtime (only pure structural helpers: `ast.ts`, `fs.ts`,
|
|
98
|
+
* `agents.ts`), so the bundle stays free of the `ai`/`@ai-sdk/*` dependency
|
|
99
|
+
* graph the main entry pulls in.
|
|
100
|
+
*/
|
|
101
|
+
declare const plugin: LintPlugin;
|
|
102
|
+
/**
|
|
103
|
+
* The full catalogue at its intended severities — spread into an oxlint
|
|
104
|
+
* `rules` map to enable everything in one line:
|
|
105
|
+
*
|
|
106
|
+
* rules: { ...recommendedRules }
|
|
107
|
+
*
|
|
108
|
+
* Hard conventions are errors; `m2w-*` (the model-id heuristic) is a warning.
|
|
109
|
+
*/
|
|
110
|
+
declare const recommendedRules: Record<string, 'error' | 'warn'>;
|
|
111
|
+
/**
|
|
112
|
+
* The composable fragment — wire the plugin and enable the whole catalogue.
|
|
113
|
+
* Designed to be composed with a base preset (e.g. `@jterrazz/typescript/oxlint`):
|
|
114
|
+
*
|
|
115
|
+
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
116
|
+
* import { intelligence } from '@jterrazz/intelligence/oxlint';
|
|
117
|
+
* export default compose(node, intelligence);
|
|
118
|
+
*
|
|
119
|
+
* `jsPlugins` registers the tool-facing entry, `rules` is {@link recommendedRules}.
|
|
120
|
+
* `overrides` ships empty (no per-glob relaxation is needed today — every rule
|
|
121
|
+
* gates itself by file path/name internally) but is kept on the fragment's
|
|
122
|
+
* shape for parity with `@jterrazz/test`'s `testing` fragment and so a future
|
|
123
|
+
* relaxation has somewhere to go without a breaking shape change.
|
|
124
|
+
*/
|
|
125
|
+
declare const intelligence: {
|
|
126
|
+
jsPlugins: string[];
|
|
127
|
+
overrides: never[];
|
|
128
|
+
rules: Record<string, "error" | "warn">;
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { plugin as default, intelligence, recommendedRules };
|
|
132
|
+
//# sourceMappingURL=oxlint.d.cts.map
|
package/dist/oxlint.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region src/lint/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Local structural types for the slice of oxlint's JS-plugin API this layer uses.
|
|
4
|
+
*
|
|
5
|
+
* oxlint does not publicly export its `Plugin` / `Rule` / `Context` types (only
|
|
6
|
+
* `RuleTester`, from `oxlint/plugins-dev`), so we describe the exact subset we
|
|
7
|
+
* depend on here. Declaring them locally keeps the rules layer importing
|
|
8
|
+
* NOTHING but these ambient shapes and pure helpers from `ast.ts`/`fs.ts` — no
|
|
9
|
+
* dependency on the AI SDK / `ai` runtime this package otherwise ships, so the
|
|
10
|
+
* oxlint bundle stays light. The API is ESLint-compatible, so these shapes
|
|
11
|
+
* mirror ESTree (mirrors `@jterrazz/test`'s `src/lint/types.ts`).
|
|
12
|
+
*/
|
|
13
|
+
/** Minimal AST node. Rules narrow by `type` and read known fields defensively. */
|
|
14
|
+
type AstNode = {
|
|
15
|
+
type: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
};
|
|
18
|
+
/** The slice of `context.sourceCode` the rules read. */
|
|
19
|
+
type SourceCode = {
|
|
20
|
+
text: string;
|
|
21
|
+
};
|
|
22
|
+
/** A diagnostic accepted by `context.report`. */
|
|
23
|
+
type Diagnostic = {
|
|
24
|
+
data?: Record<string, number | string>;
|
|
25
|
+
messageId?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
node?: AstNode;
|
|
28
|
+
};
|
|
29
|
+
/** Rule context passed to `create`. */
|
|
30
|
+
type RuleContext = {
|
|
31
|
+
filename: string;
|
|
32
|
+
id: string;
|
|
33
|
+
/** Configured rule options (`['error', …options]` minus the severity). */
|
|
34
|
+
options: readonly unknown[];
|
|
35
|
+
physicalFilename: string;
|
|
36
|
+
report: (diagnostic: Diagnostic) => void;
|
|
37
|
+
sourceCode: SourceCode;
|
|
38
|
+
};
|
|
39
|
+
/** A visitor: node-type keys → handlers invoked on entry (plus `Program:exit`). */
|
|
40
|
+
type Visitor = Record<string, (node: AstNode) => void>;
|
|
41
|
+
/**
|
|
42
|
+
* The normative documentation a rule carries — the code is the source of truth
|
|
43
|
+
* for the mechanized catalogue (docs-as-code inversion, mirroring
|
|
44
|
+
* `@jterrazz/test`). Every plugin rule sets `meta.docs` to its {@link RuleDoc}
|
|
45
|
+
* entry from `manifest.ts`; `plugin.test.ts` guards that every shipped rule
|
|
46
|
+
* carries one and that the manifest covers exactly the shipped rules.
|
|
47
|
+
*/
|
|
48
|
+
type RuleDoc = {
|
|
49
|
+
/** Convention code, e.g. `'P1'`. */
|
|
50
|
+
id: string;
|
|
51
|
+
/** The normative sentence — what the rule enforces. */
|
|
52
|
+
convention: string;
|
|
53
|
+
/** One line: why the rule exists. */
|
|
54
|
+
rationale: string;
|
|
55
|
+
};
|
|
56
|
+
/** Rule metadata (the subset we set). */
|
|
57
|
+
type RuleMeta = {
|
|
58
|
+
docs?: RuleDoc & {
|
|
59
|
+
description?: string;
|
|
60
|
+
};
|
|
61
|
+
messages?: Record<string, string>;
|
|
62
|
+
/** JSON schema for options — required by oxlint for rules that take options. */
|
|
63
|
+
schema?: false | unknown[];
|
|
64
|
+
type?: 'layout' | 'problem' | 'suggestion';
|
|
65
|
+
};
|
|
66
|
+
/** A lint rule in oxlint's `create` form. */
|
|
67
|
+
type LintRule = {
|
|
68
|
+
create: (context: RuleContext) => Visitor;
|
|
69
|
+
meta?: RuleMeta;
|
|
70
|
+
};
|
|
71
|
+
/** An oxlint JS plugin: a namespace plus its rules. */
|
|
72
|
+
type LintPlugin = {
|
|
73
|
+
meta: {
|
|
74
|
+
name: string;
|
|
75
|
+
};
|
|
76
|
+
rules: Record<string, LintRule>;
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/lint/plugin.d.ts
|
|
80
|
+
/**
|
|
81
|
+
* The `@jterrazz/intelligence` oxlint plugin — formalizes the agent/prompt
|
|
82
|
+
* folder convention documented in the README's "Agent & prompt conventions"
|
|
83
|
+
* section as statically-checkable rules, mirroring `@jterrazz/test`'s
|
|
84
|
+
* `src/lint/plugin.ts` (same composable-fragment architecture, same
|
|
85
|
+
* `RuleTester` test layer, same manifest/docs-as-code pattern).
|
|
86
|
+
*
|
|
87
|
+
* Registered in a consumer's `oxlint.config.ts` via
|
|
88
|
+
* `jsPlugins: ['@jterrazz/intelligence/oxlint']` and referenced as
|
|
89
|
+
* `intelligence/<rule>` in the `rules` map — or enabled wholesale via the
|
|
90
|
+
* {@link intelligence} composable fragment:
|
|
91
|
+
*
|
|
92
|
+
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
93
|
+
* import { intelligence } from '@jterrazz/intelligence/oxlint';
|
|
94
|
+
* export default compose(node, intelligence);
|
|
95
|
+
*
|
|
96
|
+
* Bundled by tsdown (`dist/oxlint.js`); rules import nothing from this
|
|
97
|
+
* package's AI SDK runtime (only pure structural helpers: `ast.ts`, `fs.ts`,
|
|
98
|
+
* `agents.ts`), so the bundle stays free of the `ai`/`@ai-sdk/*` dependency
|
|
99
|
+
* graph the main entry pulls in.
|
|
100
|
+
*/
|
|
101
|
+
declare const plugin: LintPlugin;
|
|
102
|
+
/**
|
|
103
|
+
* The full catalogue at its intended severities — spread into an oxlint
|
|
104
|
+
* `rules` map to enable everything in one line:
|
|
105
|
+
*
|
|
106
|
+
* rules: { ...recommendedRules }
|
|
107
|
+
*
|
|
108
|
+
* Hard conventions are errors; `m2w-*` (the model-id heuristic) is a warning.
|
|
109
|
+
*/
|
|
110
|
+
declare const recommendedRules: Record<string, 'error' | 'warn'>;
|
|
111
|
+
/**
|
|
112
|
+
* The composable fragment — wire the plugin and enable the whole catalogue.
|
|
113
|
+
* Designed to be composed with a base preset (e.g. `@jterrazz/typescript/oxlint`):
|
|
114
|
+
*
|
|
115
|
+
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
116
|
+
* import { intelligence } from '@jterrazz/intelligence/oxlint';
|
|
117
|
+
* export default compose(node, intelligence);
|
|
118
|
+
*
|
|
119
|
+
* `jsPlugins` registers the tool-facing entry, `rules` is {@link recommendedRules}.
|
|
120
|
+
* `overrides` ships empty (no per-glob relaxation is needed today — every rule
|
|
121
|
+
* gates itself by file path/name internally) but is kept on the fragment's
|
|
122
|
+
* shape for parity with `@jterrazz/test`'s `testing` fragment and so a future
|
|
123
|
+
* relaxation has somewhere to go without a breaking shape change.
|
|
124
|
+
*/
|
|
125
|
+
declare const intelligence: {
|
|
126
|
+
jsPlugins: string[];
|
|
127
|
+
overrides: never[];
|
|
128
|
+
rules: Record<string, "error" | "warn">;
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { plugin as default, intelligence, recommendedRules };
|
|
132
|
+
//# sourceMappingURL=oxlint.d.ts.map
|