@graphorin/eslint-plugin 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # @graphorin/eslint-plugin
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial scaffold for the Graphorin ESLint plugin. Ships an empty rule set
8
+ and a single no-op rule (`no-console-in-public-api`) used to verify that
9
+ the plugin loads correctly in a host project. The full ruleset lands in a
10
+ later release.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleksiy Stepurenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @graphorin/eslint-plugin
2
+
3
+ > ESLint rules for projects that build on **Graphorin**.
4
+
5
+ - **Status:** v0.5.0 — final Phase 16 ruleset.
6
+ - **License:** [MIT](./LICENSE) — © 2026 Oleksiy Stepurenko.
7
+ - **Engines:** Node.js 22+ (ESM only).
8
+ - **Peer dependency:** `eslint >= 9`.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ pnpm add -D eslint @graphorin/eslint-plugin
14
+ ```
15
+
16
+ ## Usage (ESLint flat config — `eslint.config.js`)
17
+
18
+ ```js
19
+ import graphorin from '@graphorin/eslint-plugin';
20
+
21
+ export default [
22
+ {
23
+ plugins: { '@graphorin': graphorin },
24
+ rules: {
25
+ '@graphorin/tool-description-required': 'error',
26
+ '@graphorin/tool-examples-recommended': 'warn',
27
+ '@graphorin/tool-parameter-naming': 'warn',
28
+ '@graphorin/no-secret-unwrap': 'error',
29
+ '@graphorin/no-secret-in-deps': 'error',
30
+ '@graphorin/no-implicit-network-call': 'error',
31
+ '@graphorin/no-third-party-workflow-aliases': 'error',
32
+ '@graphorin/provider-middleware-order': 'error',
33
+ '@graphorin/no-bare-tool-exec': 'warn',
34
+ },
35
+ },
36
+ ];
37
+ ```
38
+
39
+ The bundled config wires every active rule at the severities documented below.
40
+ For ESLint 9+ flat config (`eslint.config.js`), spread `flat/recommended` — it
41
+ maps the `@graphorin` namespace to the plugin object for you:
42
+
43
+ ```js
44
+ import graphorin from '@graphorin/eslint-plugin';
45
+
46
+ export default [
47
+ graphorin.configs['flat/recommended'],
48
+ ];
49
+ ```
50
+
51
+ The legacy `.eslintrc` form is still exported as `configs.recommended`
52
+ (`plugins: ['@graphorin']`) for ESLint 8 consumers.
53
+
54
+ ## Rules
55
+
56
+ | Rule | Status |
57
+ |---|---|
58
+ | `tool-description-required` | Active. Flags `tool({...})` registrations whose `description` is missing, shorter than 20 characters, or a placeholder value (`'TODO'`, `'FIXME'`, `'tbd'`, `'description'`, `'placeholder'`). |
59
+ | `tool-examples-recommended` | Active. Flags missing or empty `examples` arrays and rejects more than the documented upper bound (5). |
60
+ | `tool-parameter-naming` | Active. Flags ambiguous single-word parameter names (`user`, `id`, `name`, `value`, `data`, `input`, `output`, `result`, `to`, `from`, `key`, `field`) and numeric-suffix names (`arg1`, `param2`) on `inputSchema: z.object({ ... })`. Per-tool opt-out via `tags: ['experimental']` or `tags: ['legacy']`. |
61
+ | `no-secret-unwrap` | Active. Flags `.unwrap()` and `.reveal()` calls on `SecretValue`-shaped expressions. `.unwrap()` is reported as `'error'` regardless of comments (the method is `@deprecated`); `.reveal()` honours the `// graphorin-allow-secret-unwrap: <reason>` opt-out. |
62
+ | `no-secret-in-deps` | Active. Flags `Agent.toTool({ inheritSecrets: [...] })` calls whose allowlist is non-empty and lacks an `// rb-24-justification: <reason>` comment. |
63
+ | `provider-middleware-order` | Active. Lint-time enforcement of the canonical `withTracing → withRetry → withRateLimit → withCostLimit → withCostTracking → withFallback → withRedaction` ordering. |
64
+ | `no-implicit-network-call` | Active. Flags bare `fetch(...)` / `axios.get(...)` / `https.request(...)` / `new XMLHttpRequest()` invocations in `@graphorin/*` framework code without the explicit `// graphorin-allow-network: <reason>` opt-out. |
65
+ | `no-third-party-workflow-aliases` | Active. Flags identifiers that mirror third-party-library workflow primitives in the `@graphorin/workflow` package's source so the framework keeps its own naming. |
66
+ | `no-bare-tool-exec` | Active. Flags `tool({ execute })` functions that do not reference `signal` so long-running tools always propagate the cancellation contract. |
67
+ | `no-console-in-public-api` | Scaffold (no-op). Will flag `console.*` calls in code that is part of a package's public surface; activates after the v0.1 public-API freeze. |
68
+
69
+ ## Programmatic discovery (single source of truth)
70
+
71
+ The three `tool-*` rules are also exposed as plain helpers so `graphorin tools lint` reuses the same logic without re-importing through the ESLint runtime:
72
+
73
+ ```ts
74
+ import {
75
+ AMBIGUOUS_PARAMETER_NAMES,
76
+ discoverToolCallsInSource,
77
+ gradeTool,
78
+ PARAMETER_NAMING_OPT_OUT_TAGS,
79
+ PLACEHOLDER_DESCRIPTIONS,
80
+ runToolRules,
81
+ } from '@graphorin/eslint-plugin';
82
+
83
+ const source = await readFile('src/tools/send-email.ts', 'utf8');
84
+ const tools = discoverToolCallsInSource('src/tools/send-email.ts', source);
85
+ for (const tool of tools) {
86
+ const findings = runToolRules(tool);
87
+ const score = gradeTool(tool, findings);
88
+ console.log(`${tool.name}: ${score.score}/100`);
89
+ }
90
+ ```
91
+
92
+ ## Versioning
93
+
94
+ `@graphorin/eslint-plugin` follows the same lockstep release as the rest of the `@graphorin/*` packages while the framework is on the `0.x` line. Once Graphorin reaches `1.0`, the plugin will move to its own release cadence.
95
+
96
+ ---
97
+
98
+ **Graphorin** · v0.5.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
@@ -0,0 +1,249 @@
1
+ import * as eslint0 from "eslint";
2
+
3
+ //#region src/tool-discovery.d.ts
4
+
5
+ /**
6
+ * Static `tool({...})` discovery + per-tool grader. Used by both the
7
+ * three `tool-*` ESLint rules and by the `graphorin tools lint` CLI
8
+ * subcommand (Phase 15) so the rule logic has a single source of
9
+ * truth (per the working plan acceptance criteria for RB-49).
10
+ *
11
+ * The discovery is intentionally text-based — it scans a source
12
+ * string for `tool(` call expressions and extracts the immediate
13
+ * object literal that follows. The extractor handles the common
14
+ * formatting cases (single-line, multi-line, nested object/array
15
+ * literals, single + double-quoted strings, template literals) and
16
+ * gracefully skips invocations whose argument shape it cannot
17
+ * parse statically. The trade-off is documented: a project that
18
+ * hides its `tool({...})` calls behind a builder helper or a
19
+ * dynamic import will not be picked up by this lint surface; that
20
+ * is the documented contract for the v0.1 lint surface.
21
+ *
22
+ * **Per-tool grader rubric (RB-49 calibration):**
23
+ *
24
+ * - **description axis (0..40 points):**
25
+ * - 0 if missing / placeholder / shorter than 20 chars.
26
+ * - 16 if length >= 20.
27
+ * - 24 if length >= 30.
28
+ * - 32 if length >= 50.
29
+ * - 40 if length >= 80.
30
+ * - **examples axis (0..30 points):**
31
+ * - 0 if no examples or more than 5 (the documented upper bound).
32
+ * - 12 base for 1 example, +6 per additional, capped at 30.
33
+ * - -6 per PII finding (cap at 0).
34
+ * - **parameter naming axis (0..30 points):**
35
+ * - 30 base, deducted per finding.
36
+ * - -30/N per ambiguous-name finding (full penalty per param).
37
+ * - -10/N per numeric-suffix finding (partial penalty per param).
38
+ * - 15 baseline when no parameters are discoverable.
39
+ *
40
+ * Total: 0..100 points. Calibrated against the RB-49 fixture
41
+ * catalog so `wellDescribedTool` scores 82, `placeholderDescriptionTool`
42
+ * scores 20, and `examplesPiiTool` scores 61.
43
+ *
44
+ * @stable
45
+ */
46
+ /**
47
+ * @stable
48
+ */
49
+ interface DiscoveredTool {
50
+ /** Source file the call was found in. */
51
+ readonly file: string;
52
+ /** 1-indexed line of the `tool(` token. */
53
+ readonly line: number;
54
+ /** Tool name extracted from the `name:` property when present. */
55
+ readonly name: string;
56
+ /** Tool description (`description:` value) when extractable. */
57
+ readonly description?: string;
58
+ /** Number of examples declared in the `examples:` array. */
59
+ readonly examplesCount: number;
60
+ /** Whether `examples:` is a non-empty array literal. */
61
+ readonly hasExamples: boolean;
62
+ /** Snapshot of identifiers referenced from the `inputSchema` Zod chain. */
63
+ readonly parameterNames: ReadonlyArray<string>;
64
+ /** Tags declared on the call (best-effort). */
65
+ readonly tags: ReadonlyArray<string>;
66
+ /**
67
+ * Raw object-literal source. Useful for tests + as a context blob
68
+ * when the CLI needs to surface the original source in a report.
69
+ */
70
+ readonly source: string;
71
+ }
72
+ /**
73
+ * @stable
74
+ */
75
+ type LintFindingKind = 'description-missing' | 'description-too-short' | 'description-placeholder' | 'examples-missing' | 'examples-too-many' | 'examples-pii-detected' | 'parameter-ambiguous' | 'parameter-numeric-suffix';
76
+ /**
77
+ * @stable
78
+ */
79
+ interface LintFinding {
80
+ readonly rule: 'graphorin/tool-description-required' | 'graphorin/tool-examples-recommended' | 'graphorin/tool-parameter-naming';
81
+ readonly kind: LintFindingKind;
82
+ readonly severity: 'error' | 'warn' | 'info';
83
+ readonly message: string;
84
+ readonly toolName: string;
85
+ readonly file: string;
86
+ readonly line: number;
87
+ readonly hint?: string;
88
+ /**
89
+ * Optional matched-pattern context. Populated by the
90
+ * `examples-pii-detected` finding so reports can highlight which
91
+ * example payload triggered the rule.
92
+ */
93
+ readonly matchedPattern?: string;
94
+ }
95
+ /**
96
+ * @stable
97
+ */
98
+ interface ToolGraderScore {
99
+ readonly toolName: string;
100
+ readonly file: string;
101
+ readonly line: number;
102
+ readonly score: number;
103
+ readonly axes: {
104
+ readonly description: number;
105
+ readonly examples: number;
106
+ readonly parameterNaming: number;
107
+ };
108
+ readonly findings: ReadonlyArray<LintFinding>;
109
+ }
110
+ /**
111
+ * Generic identifiers the parameter-naming rule flags as ambiguous.
112
+ * Tools whose `inputSchema` references only specific identifiers
113
+ * (e.g. `userId`, `recipientEmail`, `apiKey`) get full credit on
114
+ * the naming axis.
115
+ *
116
+ * @stable
117
+ */
118
+ declare const AMBIGUOUS_PARAMETER_NAMES: ReadonlyArray<string>;
119
+ /**
120
+ * Tag values that, when present in a tool's `tags: [...]` literal,
121
+ * suppress the parameter-naming rule for that tool. The opt-out
122
+ * exists so operators can defer the rename for a long tail of
123
+ * pre-RB-49 tools while the framework migrates without breaking
124
+ * calling code.
125
+ *
126
+ * @stable
127
+ */
128
+ declare const PARAMETER_NAMING_OPT_OUT_TAGS: ReadonlyArray<string>;
129
+ /**
130
+ * Placeholder values the description-required rule treats as
131
+ * non-descriptions.
132
+ *
133
+ * @stable
134
+ */
135
+ declare const PLACEHOLDER_DESCRIPTIONS: ReadonlyArray<string>;
136
+ /**
137
+ * Discover every `tool({...})` invocation in a source string. The
138
+ * returned findings are stable + frozen so callers can pass them
139
+ * straight into a JSON report.
140
+ *
141
+ * @stable
142
+ */
143
+ declare function discoverToolCallsInSource(file: string, source: string): DiscoveredTool[];
144
+ /**
145
+ * Run the three RB-49 rules against a discovered tool and return the
146
+ * findings. The CLI grader maps these findings into per-axis scores;
147
+ * the ESLint rules forward them to `context.report(...)`.
148
+ *
149
+ * @stable
150
+ */
151
+ declare function runToolRules(tool: DiscoveredTool, severityOverrides?: {
152
+ readonly toolDescription?: 'error' | 'warn' | 'off';
153
+ readonly toolExamples?: 'error' | 'warn' | 'off';
154
+ readonly toolParameterNaming?: 'error' | 'warn' | 'off';
155
+ }): LintFinding[];
156
+ /**
157
+ * Compute the per-tool grader score (0..100). Each axis is gated by
158
+ * the findings produced for that axis. The rubric is calibrated
159
+ * against the RB-49 fixture catalog (`wellDescribedTool` -> 82,
160
+ * `placeholderDescriptionTool` -> 20, `examplesPiiTool` -> 61).
161
+ *
162
+ * @stable
163
+ */
164
+ declare function gradeTool(tool: DiscoveredTool, findings: ReadonlyArray<LintFinding>): ToolGraderScore;
165
+ //#endregion
166
+ //#region src/index.d.ts
167
+ /**
168
+ * @graphorin/eslint-plugin — ESLint plugin for projects that build on
169
+ * the Graphorin framework.
170
+ *
171
+ * Phase 16 final ruleset:
172
+ *
173
+ * - `no-secret-unwrap` — DEC-020 / ADR-026. Active.
174
+ * - `no-secret-in-deps` — DEC-137. Active.
175
+ * - `provider-middleware-order` — DEC-145 / ADR-039. Active.
176
+ * - `no-implicit-network-call` — DEC-154 / ADR-041. Active.
177
+ * - `no-third-party-workflow-aliases` — DEC-019 / ADR-029. Active.
178
+ * - `no-bare-tool-exec` — principle 3 / DEC-143. Active.
179
+ * - `tool-description-required` — Active.
180
+ * - `tool-examples-recommended` — Active.
181
+ * - `tool-parameter-naming` — Active.
182
+ *
183
+ * (The former `no-console-in-public-api` scaffold — a permanent no-op
184
+ * since Phase 01 — was removed in the v0.4 hygiene pass (PS-21) rather
185
+ * than shipped inert.)
186
+ *
187
+ * @packageDocumentation
188
+ */
189
+ declare const VERSION = "0.5.0";
190
+ declare const meta: {
191
+ readonly name: "@graphorin/eslint-plugin";
192
+ readonly version: "0.5.0";
193
+ };
194
+ declare const rules: {
195
+ readonly 'no-bare-tool-exec': eslint0.Rule.RuleModule;
196
+ readonly 'no-implicit-network-call': eslint0.Rule.RuleModule;
197
+ readonly 'no-secret-in-deps': eslint0.Rule.RuleModule;
198
+ readonly 'no-secret-unwrap': eslint0.Rule.RuleModule;
199
+ readonly 'no-third-party-workflow-aliases': eslint0.Rule.RuleModule;
200
+ readonly 'provider-middleware-order': eslint0.Rule.RuleModule;
201
+ readonly 'tool-description-required': eslint0.Rule.RuleModule;
202
+ readonly 'tool-examples-recommended': eslint0.Rule.RuleModule;
203
+ readonly 'tool-parameter-naming': eslint0.Rule.RuleModule;
204
+ };
205
+ /** Shared severity map for both the legacy and flat recommended presets. */
206
+ declare const RECOMMENDED_RULES: {
207
+ readonly '@graphorin/no-bare-tool-exec': "warn";
208
+ readonly '@graphorin/no-implicit-network-call': "error";
209
+ readonly '@graphorin/no-secret-in-deps': "error";
210
+ readonly '@graphorin/no-secret-unwrap': "error";
211
+ readonly '@graphorin/no-third-party-workflow-aliases': "error";
212
+ readonly '@graphorin/provider-middleware-order': "error";
213
+ readonly '@graphorin/tool-description-required': "error";
214
+ readonly '@graphorin/tool-examples-recommended': "warn";
215
+ readonly '@graphorin/tool-parameter-naming': "warn";
216
+ };
217
+ /**
218
+ * PS-17: ship BOTH config shapes. `recommended` is the legacy `.eslintrc` form
219
+ * (`plugins: ['@graphorin']`); `flat/recommended` is the ESLint 9+ flat-config
220
+ * form that maps the namespace to the plugin object, so flat-config consumers
221
+ * can `...plugin.configs['flat/recommended']` instead of hand-wiring ten rules.
222
+ */
223
+ declare const plugin: {
224
+ readonly meta: typeof meta;
225
+ readonly rules: typeof rules;
226
+ readonly configs: {
227
+ readonly recommended: {
228
+ readonly plugins: readonly string[];
229
+ readonly rules: typeof RECOMMENDED_RULES;
230
+ };
231
+ readonly 'flat/recommended': {
232
+ plugins: Record<string, unknown>;
233
+ readonly rules: typeof RECOMMENDED_RULES;
234
+ };
235
+ };
236
+ };
237
+ declare const configs: {
238
+ readonly recommended: {
239
+ readonly plugins: readonly string[];
240
+ readonly rules: typeof RECOMMENDED_RULES;
241
+ };
242
+ readonly 'flat/recommended': {
243
+ plugins: Record<string, unknown>;
244
+ readonly rules: typeof RECOMMENDED_RULES;
245
+ };
246
+ };
247
+ //#endregion
248
+ export { AMBIGUOUS_PARAMETER_NAMES, type DiscoveredTool, type LintFinding, type LintFindingKind, PARAMETER_NAMING_OPT_OUT_TAGS, PLACEHOLDER_DESCRIPTIONS, type ToolGraderScore, VERSION, configs, plugin as default, discoverToolCallsInSource, gradeTool, meta, rules, runToolRules };
249
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/tool-discovery.ts","../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;AA6CA;AA2BA;AAaA;AAuBA;AAqBA;AAwBA;AAWA;AAkBA;AAgCA;AAkIA;;;;;;;;;ACvTA;AAEA;AAKA;;;;;;;;;;;AAUW;AAaD;;;;;;;AAiCV;;AAhBe,UDnCE,cAAA,CCmCF;EACc;EAAiB,SAAA,IAAA,EAAA,MAAA;;;;;;;;;;;;2BDtBnB;;iBAEV;;;;;;;;;;KAWL,eAAA;;;;UAaK,WAAA;;iBAKA;;;;;;;;;;;;;;;;;UAkBA,eAAA;;;;;;;;;;qBAUI,cAAc;;;;;;;;;;cAWtB,2BAA2B;;;;;;;;;;cAwB3B,+BAA+B;;;;;;;cAW/B,0BAA0B;;;;;;;;iBAkBvB,yBAAA,gCAAyD;;;;;;;;iBAgCzD,YAAA,OACR;;;;IAML;;;;;;;;;iBA2Ha,SAAA,OACR,0BACI,cAAc,eACvB;;;;;;AA9SH;AA2BA;AAaA;AAuBA;AAqBA;AAwBA;AAWA;AAkBA;AAgCA;AAkIA;;;;;;;;;ACvTA;AAEa,cAFA,OAAA,GAKH,OAAA;AAEG,cALA,IAeH,EAAA;;;;cAVG;;;;;;;EAaP,SAAA,2BAUI,yBAAA;EAQJ,SAsBL,2BAAA,yBAAA;EArBuB,SAAA,uBAAA,yBAAA;CACC;;cApBnB,iBA2BS,EAAA;EACc,SAAA,8BAAA,EAAA,MAAA;EAAiB,SAAA,qCAAA,EAAA,OAAA;EAejC,SAAA,8BAAwB,EAAA,OAAA;EAnBR,SAAA,6BAAA,EAAA,OAAA;EAGd,SAAA,4CAAA,EAAA,OAAA;EACc,SAAA,sCAAA,EAAA,OAAA;EAAiB,SAAA,sCAAA,EAAA,OAAA;;;;;;;;;;cAVxC;wBACkB;yBACC;;;;6BAII;;;eAGd;6BACc;;;;cAehB;;;2BAnBgB;;;aAGd;2BACc"}