diff --git a/.bob/rules-agent/AGENTS.md b/.bob/rules-agent/AGENTS.md new file mode 100644 index 00000000..2a430467 --- /dev/null +++ b/.bob/rules-agent/AGENTS.md @@ -0,0 +1,61 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Critical Coding Rules + +- Every source file **must** start with the IBM copyright block (`Copyright IBM Corporation. / SPDX-License-Identifier: Apache2.0`) +- Rule implementations (`functions/`) and rule definitions (`rules/`) are separate files — both are required for every rule; adding only one will cause test failures +- Both `functions/index.js` and `rules/index.js` are manual barrel files — new entries must be added to both +- New rules must also be registered in `packages/ruleset/src/ibm-oas.js` +- Rule `given` JSONPath expressions must NOT use implicit `.[]` recursion (e.g., `$.paths.[*]` is invalid; use `$.paths[*]`) — enforced by `test/meta/rule-style.test.js` + +## Rule Function Conventions + +- The logger must be lazily initialized at first call using `context.rule.name` (the rule ID is not available at module load time): + ```js + let ruleId, logger; + module.exports = function(input, _opts, context) { + if (!logger) { + ruleId = context.rule.name; + logger = LoggerFactory.getInstance().getLogger(ruleId); + } + }; + ``` +- Rule functions return an array of `{ message, path }` objects (or `[]`), not throw errors +- Import shared utilities from `@ibm-cloud/openapi-ruleset-utilities`, internal utils from `'../utils'` +- Use `validateNestedSchemas()` / `validateComposedSchemas()` / `validateSubschemas()` from utilities to recurse into schemas (don't manually recurse) + +## Adding Rules: Required Checklist + +1. `src/functions/.js` — implementation +2. `src/rules/.js` — Spectral rule object +3. Export in `src/functions/index.js` +4. Export in `src/rules/index.js` +5. Register (with severity) in `src/ibm-oas.js` +6. Test file at `test/rules/.test.js` +7. Scoring rubric entry in `packages/validator/src/scoring-tool/rubric.js` +8. Documentation entry in `docs/ibm-cloud-rules.md` + +Missing any step causes silent omission from the ruleset, test failures, or an unscored rule. + +## Test Patterns + +- Always start from `makeCopy(rootDocument)` and mutate — never mutate `rootDocument` directly +- Use `testRule(ruleId, rule, document)` for integration-style rule tests (full Spectral pipeline) +- Use `unitTestRule(ruleId, rule, input)` when you need to bypass `given`/`formats` (e.g., testing the function directly with a partial schema) +- Severity codes are numbers: `error=0, warning=1, info=2, hint=3` — assert with `severityCodes.error` not the string `'error'` +- `all-schemas-document.js` provides a document with schemas in every possible location — useful for coverage tests + +## Running Tests + +```bash +# Single test file from root +npm run jest --workspace packages/ruleset -- --testPathPattern="string-attributes" + +# All tests in a workspace +npm run test-ruleset + +# With verbose output +cd packages/ruleset && npx jest --verbose test/rules/string-attributes.test.js +``` diff --git a/.bob/rules-ask/AGENTS.md b/.bob/rules-ask/AGENTS.md new file mode 100644 index 00000000..9c1b097a --- /dev/null +++ b/.bob/rules-ask/AGENTS.md @@ -0,0 +1,26 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Codebase Navigation + +- `packages/ruleset/src/functions/` — rule *implementations* (the JS functions Spectral calls) +- `packages/ruleset/src/rules/` — rule *definitions* (Spectral rule objects with `given`, `severity`, etc.) +- These are intentionally separate: a function can be reused across multiple rules +- `packages/utilities/src/collections/index.js` — canonical JSONPath location collections reused by many rules (e.g., `requestBodySchemas`, `responseSchemas`, `schemas`) +- `packages/ruleset/src/utils/` — ruleset-private utilities (not exported publicly) +- `packages/utilities/src/utils/` — public utilities exported as `@ibm-cloud/openapi-ruleset-utilities` + +## Documentation Locations + +- Rule docs: `docs/ibm-cloud-rules.md` (main reference for all IBM Cloud rules) +- Migration guide: `Migration-Guide.md` +- Utilities API: auto-generated from JSDoc via `npm run generate-utilities-docs` +- Types for utilities: auto-generated via `npm run generate-utilities-types` (outputs to `packages/utilities/types/`) + +## Counterintuitive Structure + +- The root `package.json` version (`0.0.0`) is a placeholder — actual package versions are in each workspace's `package.json` +- `packages/ruleset/src/ibm-oas.js` is the entry point for the ruleset (`main` field), not `src/index.js` +- The `app/` directory at the root is unrelated to the npm packages — it contains separate application code +- Test utilities at `packages/ruleset/test/test-utils/` are not published; `root-document.js` is the shared baseline API for all rule tests diff --git a/.bob/rules-plan/AGENTS.md b/.bob/rules-plan/AGENTS.md new file mode 100644 index 00000000..6027e785 --- /dev/null +++ b/.bob/rules-plan/AGENTS.md @@ -0,0 +1,40 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Architectural Constraints + +- **Rules are stateless** — Spectral re-runs rule functions per document node; no state should be stored outside the lazy logger/ruleId initialization pattern +- **Functions vs Rules separation is intentional** — a single function (e.g., `enumCasingConvention`) can back multiple rule definitions; don't merge them +- **`resolved: true` vs `resolved: false`** in rule definitions changes whether `$ref`s are dereferenced before the function runs — rules using `validateNestedSchemas()` require `resolved: true` (it cannot traverse `$ref`s) +- **`formats: [oas3]`** on a rule means it only runs for OpenAPI 3.x; omitting `formats` runs it on all versions — most IBM rules should specify `oas3` +- The ruleset extends Spectral's built-in `oas` ruleset with overrides; some Spectral rules are deliberately turned off in `ibm-oas.js` + +## Dependency Architecture + +``` +packages/validator + └── @ibm-cloud/openapi-ruleset (packages/ruleset) + └── @ibm-cloud/openapi-ruleset-utilities (packages/utilities) +``` + +Circular dependencies would break workspace linking — utilities must have no dependency on ruleset or validator. + +## Adding Rules: Required Checklist + +1. `src/functions/.js` — implementation +2. `src/rules/.js` — Spectral rule object +3. Export in `src/functions/index.js` +4. Export in `src/rules/index.js` +5. Register (with severity) in `src/ibm-oas.js` +6. Test file at `test/rules/.test.js` +7. Scoring rubric entry in `packages/validator/src/scoring-tool/rubric.js` +8. Documentation entry in `docs/ibm-cloud-rules.md` + +Missing any step causes silent omission from the ruleset, test failures, or an unscored rule. + +## Commit & Release + +- Commits must follow Angular commit message format (`feat:`, `fix:`, `docs:`, etc.) +- `semantic-release` auto-generates CHANGELOG and publishes to npm based on commit messages +- Breaking changes require `BREAKING CHANGE:` footer in commit body to trigger major version bump diff --git a/.bob/skills/add-openapi-rule/SKILL.md b/.bob/skills/add-openapi-rule/SKILL.md new file mode 100644 index 00000000..64d302fa --- /dev/null +++ b/.bob/skills/add-openapi-rule/SKILL.md @@ -0,0 +1,421 @@ +--- +name: add-openapi-rule +description: > + Use when the user wants to add a new lint rule to the IBM OpenAPI Validator project + (openapi-validator / ibm-cloud/openapi-ruleset). Walks through creating the function file, + rule definition file, barrel exports, ibm-oas registration, test file, scoring rubric entry, + and documentation — in the correct order with all required conventions applied. +--- + +# Add a New Rule to the IBM OpenAPI Validator + +Follow these steps in order. Do not skip any step; missing any one of them will cause test failures +or leave the rule undocumented / unscored. + +--- + +## Step 0 — Gather Inputs Interactively + +**Before doing anything else**, send the user a single message that: +1. Asks for the rule description (if not already given). +2. Based on the description — whether supplied by the user or inferred from their request — + proposes concrete values for every field below and asks the user to confirm or adjust. + +Do NOT proceed to Step 1 until the user has confirmed all four items. + +### Fields to confirm + +1. **Description** — plain English, one sentence. What is the violation? + - If the user already described the rule, restate it as a clean one-sentence description and + ask them to confirm. + +2. **Rule name** — kebab-case, no `ibm-` prefix (that is added automatically). + - Derive a short, specific name from the description (e.g. `no-nullable-properties`, + `operation-summary-exists`, `schema-description-exists`). + - The full rule ID will be `ibm-`. + +3. **Severity** — propose the most appropriate one based on the description: + - `error` — structural mistake that will break SDKs or clients + - `warn` *(most common)* — convention or style violation + - `info` — informational; no immediate impact + - `hint` — rarely used + +4. **Scoring rubric** — propose values based on the description: + - `coefficient` — `1` for typical rules; `2`–`3` for high-impact structural rules + - `denominator` — what the score is measured against: + - `schemas` if the rule targets schema definitions + - `operations` if the rule targets operations or request/response behaviour + - `paths` if the rule targets path-level structure + - `categories` — one or more of `usability`, `security`, `robustness`, `evolution`; suggest + the best fit(s) based on the rule's purpose: + - `usability` — readability, discoverability, developer experience + - `security` — auth, credentials, sensitive data + - `robustness` — correctness, completeness, error handling + - `evolution` — versioning, backward compatibility + - If the rule has no meaningful impact on API quality scoring, propose a comment-only entry. + +--- + +## Step 1 — Check for Existing Coverage + +**Before writing any code**, check the existing ruleset for potential overlap. + +### 1a — Read the live rule list + +Run this command to get the current rule IDs and descriptions directly from source: + +```bash +grep -rh "description:" packages/ruleset/src/rules/*.js | grep -v "^description:$" | sort -u +``` + +Also list the rule filenames to get their IDs: + +```bash +ls packages/ruleset/src/rules/*.js | grep -v index.js | sed 's|.*/||; s|\.js$||' | sort +``` + +Use the output — not any cached list — to check for overlap with the user's described violation. + +### 1b — Decide: new rule or extend existing? + +After reviewing the list, present one of these outcomes to the user: + +**If the description overlaps significantly with an existing rule**, say so: +> "This sounds similar to `ibm-` which already checks ``. +> Would you like to: +> (a) extend that rule's function to cover your case too, or +> (b) create a separate rule anyway (e.g. different severity or `given` scope)?" +> +> If the user chooses (a), read `packages/ruleset/src/functions/.js` and +> `packages/ruleset/test/rules/.test.js` before proceeding — the extension goes +> into those files instead of creating new ones. Skip Steps 2, 3, 4, 5 (new files only) and adapt +> accordingly. + +**If there is no meaningful overlap**, confirm: +> "No existing rule covers this. Proceeding with a new rule `ibm-`." + +--- + +## Step 2 — Create the Function File + +Path: `packages/ruleset/src/functions/.js` + +Rules: +- Start with the IBM copyright block (use the current year). +- Lazy-initialize `ruleId` and `logger` at first call via `context.rule.name`. +- Return an array of `{ message, path }` objects, or `[]`. +- Import shared schema traversal helpers from `@ibm-cloud/openapi-ruleset-utilities` + (e.g. `validateNestedSchemas`, `isStringSchema`). +- Import internal utilities via `require('../utils')` (e.g. `LoggerFactory`). +- Use `validateNestedSchemas()` / `validateComposedSchemas()` / `validateSubschemas()` to recurse — + never manually recurse into schemas. +- JSDoc `@param` and `@returns` on every exported function. + +Template: + +```js +/** + * Copyright IBM Corporation. + * SPDX-License-Identifier: Apache2.0 + */ + +const { LoggerFactory } = require('../utils'); + +let ruleId; +let logger; + +/** + * + * @param {object} input - the resolved schema/path/operation node + * @param {object} _opts - rule options (unused unless the rule has options) + * @param {object} context - Spectral context (path, rule) + * @returns {Array} array of { message, path } violation objects, or [] + */ +module.exports = function (input, _opts, context) { + if (!logger) { + ruleId = context.rule.name; + logger = LoggerFactory.getInstance().getLogger(ruleId); + } + + const errors = []; + + // TODO: implement check logic here + + return errors; +}; +``` + +--- + +## Step 3 — Create the Rule Definition File + +Path: `packages/ruleset/src/rules/.js` + +Rules: +- Import the collection or JSONPath for `given` from + `@ibm-cloud/openapi-ruleset-utilities/src/collections` where possible. +- The `given` JSONPath must NOT use implicit `.[]` recursion + (e.g. `$.paths.[*]` is invalid; use `$.paths[*]`). +- Import the function by its camelCase export name from `'../functions'`. +- `message` should be `'{{error}}'` (delegates to the function's returned message string). + +Template: + +```js +/** + * Copyright IBM Corporation. + * SPDX-License-Identifier: Apache2.0 + */ + +const { } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); +const { oas3 } = require('@stoplight/spectral-formats'); +const { } = require('../functions'); + +module.exports = { + description: '', + message: '{{error}}', + severity: '', + formats: [oas3], + resolved: true, + given: , + then: { + function: , + }, +}; +``` + +--- + +## Step 4 — Register in Barrel Files + +Both barrel files are **manually maintained** — new entries must be added in alphabetical order. + +### `packages/ruleset/src/functions/index.js` + +Add one line in alphabetical order: +```js +: require('./'), +``` + +### `packages/ruleset/src/rules/index.js` + +Add one line in alphabetical order: +```js +: require('./'), +``` + +Read the current contents of both files first with `read_file` to find the correct insertion point. + +--- + +## Step 5 — Register in `ibm-oas.js` + +Path: `packages/ruleset/src/ibm-oas.js` + +Add one line under the `// IBM Custom Rules` section, in alphabetical order by rule ID: +```js +'ibm-': ibmRules., +``` + +Read the current file first to find the correct insertion point. + +--- + +## Step 6 — Create the Test File + +Path: `packages/ruleset/test/rules/.test.js` + +Rules: +- Start with the IBM copyright block. +- Import from `'../../src/rules'` (not from the package). +- Import test utilities from `'../test-utils'`: `{ makeCopy, rootDocument, testRule, unitTestRule, severityCodes }`. +- Always start with `makeCopy(rootDocument)` and mutate — never mutate `rootDocument` directly. +- Use `testRule(ruleId, rule, document)` for integration-style tests. +- Use `unitTestRule(ruleId, rule, input)` only when you need to bypass `given`/`formats`. +- Severity codes are numbers: `severityCodes.error` (0), `severityCodes.warning` (1), etc. +- Provide at least: one "clean spec passes" test and one test per violation type. + +Template: + +```js +/** + * Copyright IBM Corporation. + * SPDX-License-Identifier: Apache2.0 + */ + +const { } = require('../../src/rules'); +const { + makeCopy, + rootDocument, + testRule, + unitTestRule, + severityCodes, +} = require('../test-utils'); + +const rule = ; +const ruleId = 'ibm-'; +const expectedSeverity = severityCodes.; + +describe(`Spectral rule: ${ruleId}`, () => { + describe('Should not yield errors', () => { + it('Clean spec', async () => { + const results = await testRule(ruleId, rule, rootDocument); + expect(results).toHaveLength(0); + }); + + // Add more passing cases here + }); + + describe('Should yield errors', () => { + it('', async () => { + const testDocument = makeCopy(rootDocument); + // mutate testDocument to trigger the rule + const results = await testRule(ruleId, rule, testDocument); + expect(results).toHaveLength(); + for (const r of results) { + expect(r.severity).toBe(expectedSeverity); + expect(r.message).toMatch(//); + } + }); + }); +}); +``` + +--- + +## Step 7 — Add Scoring Rubric Entry + +Path: `packages/validator/src/scoring-tool/rubric.js` + +Every IBM rule should have a scoring entry. Add it in alphabetical order by rule ID using the +`coefficient`, `denominator`, and `categories` values confirmed in Step 0: + +```js +'ibm-': { + coefficient: , // weight of the rule in scoring (from Step 0; default 1) + denominator: '', // from Step 0 + categories: [''], // from Step 0 +}, +``` + +If the user indicated no meaningful impact on scoring, add a comment entry instead: +```js +// 'ibm-' - no impact +``` + +Read the file first to find the correct alphabetical insertion point near the surrounding `ibm-valid-*` / `ibm-well-*` entries. + +--- + +## Step 8 — Update Documentation + +Path: `docs/ibm-cloud-rules.md` + +Three locations must be updated: + +### 8a — Table of Contents + +Find the alphabetical TOC list (the bullet list under ``). Add: +```markdown + * [ibm-](#ibm-) +``` +in alphabetical order among the other `ibm-*` entries. + +### 8b — Summary Table + +Find the HTML summary table (rows of `` entries, one per rule). Add a `` block +in alphabetical order: +```html + +ibm- + + +oas3 + +``` + +### 8c — Full Rule Section + +Append a full `### ibm-` section at the correct alphabetical position among the other +`###` rule sections. Follow the exact HTML table format used by neighbouring rules. Include: +- Rule ID +- Description (a few sentences) +- Severity +- OAS Versions +- A non-compliant example (YAML code block) +- A compliant example (YAML code block) + +Template (copy the structure from any nearby rule section, e.g. `### ibm-valid-path-segments`): + +```markdown +### ibm- + + + + + + + + + + + + + + + + + + + + + + + + + +
Rule id:ibm-
Description: +
Severity:
OAS Versions:oas3
Non-compliant example: +
+
+
+
Compliant example: +
+
+
+
+``` + +--- + +## Step 9 — Run Tests and Lint + +After all files and docs are updated, run: + +```bash +# Run the new rule's tests +npm run jest --workspace packages/ruleset -- --testPathPattern="" + +# Run the full meta/style check (validates JSONPath expressions in all rules) +npm run jest --workspace packages/ruleset -- --testPathPattern="rule-style" + +# Auto-fix lint/formatting +npm run fix +``` + +Fix any failures before reporting completion. If `rule-style` fails, the `given` JSONPath in +the rule definition is invalid — check for implicit `.[]` recursion. + +--- + +## Step 10 — Report Completion + +Summarise: +- Files created (function, rule, test) +- Lines added in `functions/index.js`, `rules/index.js`, and `ibm-oas.js` +- Rubric entry added in `rubric.js` +- Documentation updated in `docs/ibm-cloud-rules.md` (TOC, summary table, rule section) +- Test results (pass/fail counts) + +Remind the user that commits must follow Angular commit message guidelines +(e.g. `feat(ruleset): add ibm- rule`). diff --git a/.gitignore b/.gitignore index 13ecf00e..516f49b0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,3 @@ coverage/ .spectral.js .nvmrc .vscode/ -.bob/ diff --git a/AGENTS.md b/AGENTS.md index e81e1f42..aba2885d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,7 +75,7 @@ Each rule has two separate files that must be kept in sync: Rule functions use a lazy-initialized logger from `LoggerFactory` (singleton via global): ```js let ruleId, logger; -module.exports = function myRule(input, options, context) { +module.exports = function myRule(input, _opts, context) { if (!logger) { ruleId = context.rule.name; logger = LoggerFactory.getInstance().getLogger(ruleId); @@ -90,11 +90,18 @@ LoggerFactory.getInstance().addLoggerSetting(ruleId, 'debug'); ## Adding a New Rule +The canonical checklist for adding a rule is in `.bob/skills/add-openapi-rule/SKILL.md`. +That skill is the single source of truth for the full workflow (function, rule definition, +barrel exports, ibm-oas registration, test, scoring rubric, documentation). + +Quick reference: 1. Create `packages/ruleset/src/functions/.js` (implementation) 2. Create `packages/ruleset/src/rules/.js` (Spectral rule object) 3. Export from `packages/ruleset/src/functions/index.js` and `packages/ruleset/src/rules/index.js` 4. Register in `packages/ruleset/src/ibm-oas.js` 5. Create test at `packages/ruleset/test/rules/.test.js` +6. Add scoring entry in `packages/validator/src/scoring-tool/rubric.js` +7. Document in `docs/ibm-cloud-rules.md` (TOC, summary table, full rule section) ## Commits