Skip to content

Commit c120b4e

Browse files
Runes tunes
1 parent 064a513 commit c120b4e

6 files changed

Lines changed: 326 additions & 0 deletions

File tree

.claude/rules/architecture.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
paths:
3+
- "src/**/*.ts"
4+
---
5+
6+
# Architecture of openapi-to-cli (ocli)
7+
8+
`ocli` is a TypeScript/Node CLI that converts OpenAPI/Swagger specs into runtime CLI commands. No code generation: at every invocation it loads a cached spec and builds command definitions on the fly.
9+
10+
## Data flow
11+
12+
```
13+
ocli profiles add <name> --api-base-url ... --openapi-spec ...
14+
|
15+
v
16+
ConfigLocator -> .ocli/ dir (global or local)
17+
|
18+
v
19+
ProfileStore -> profiles.ini (read/write/select)
20+
|
21+
v
22+
OpenapiLoader -> fetches spec, caches under .ocli/specs/<profile>.json
23+
|
24+
v
25+
OpenapiToCommands -> parses spec, applies include/exclude filters,
26+
builds CliCommand[] (name, method, path, options, body schema)
27+
|
28+
v
29+
CommandSearch (BM25) -> ranks commands by NL query or regex
30+
|
31+
v
32+
cli.ts (yargs) -> resolves profile + spec, dispatches: profiles | use | commands | <toolName>
33+
|
34+
v
35+
HttpClient (axios) -> performs the real HTTP request to API_BASE_URL
36+
```
37+
38+
## Components (mapping to `src/`)
39+
40+
- `config.ts` - `ConfigLocator`. Finds `.ocli/` (global `~/.ocli/`, local in CWD), resolves `profiles.ini` paths.
41+
- `profile-store.ts` - `ProfileStore`, `Profile`. Reads/writes `profiles.ini`, tracks current profile, validates fields.
42+
- `openapi-loader.ts` - `OpenapiLoader`. Loads spec from URL or local file, caches it to `.ocli/specs/<profile>.json`, refreshes on demand. Resolves external `$ref` across multi-file specs.
43+
- `openapi-to-commands.ts` - `OpenapiToCommands`, `CliCommand`, `CliCommandOption`. Walks the spec, applies include/exclude filters, expands path-level params, resolves local `$ref`, builds command names with optional prefix, expands `enum`/`default`/`nullable`/`oneOf` schema hints for `--help`.
44+
- `command-search.ts` - `CommandSearch`. BM25 over `(name, method, path, description, options[].name)`, plus regex fallback. Same engine used by both `ocli commands` and any future agent skill.
45+
- `bm25.ts` - tokenizer + BM25 scorer, no I/O.
46+
- `cli.ts` - `ocli` entry point. yargs command tree: `profiles add|remove|list`, `use`, `commands`, and dynamic per-spec commands. Builds the `axios` request from a `CliCommand` + parsed args; injects auth, custom headers, server URL overrides.
47+
- `version.ts` - generated by `scripts/generate-version.js` during `prebuild`. Do not edit by hand.
48+
49+
## Design principles
50+
51+
1. **OpenAPI-driven**: commands and their options come from the spec. No hand-maintained registry.
52+
2. **Profiles**: every API connection is named; `profiles.ini` is the source of truth. Global vs local `.ocli/` priority is decided by `ConfigLocator`.
53+
3. **Spec cache**: never re-download a spec on every invocation. Refresh is explicit (`onboard`/profile add or refresh flag).
54+
4. **Pure transform layer**: `bm25.ts`, `openapi-to-commands.ts`, and `command-search.ts` perform no I/O; they take inputs and return outputs. This keeps them trivially unit-testable.
55+
5. **Side effects at the edges**: filesystem in `config.ts`/`profile-store.ts`/`openapi-loader.ts`, network in `cli.ts` via `HttpClient`. Inject these via constructors (`fs`, `httpClient`) so tests can swap them.
56+
6. **TypeScript strict**: `strict: true` in `tsconfig.json`. Explicit types for exported functions and public interfaces.
57+
7. **No surprise breaking changes**: every CLI-visible change must be reflected in `README.md` and `CHANGELOG.md`.
58+
59+
## Layers and allowed dependencies
60+
61+
```
62+
Layer 0 (pure) bm25.ts, version.ts, types in openapi-to-commands.ts
63+
Layer 1 (I/O wrappers) config.ts, profile-store.ts, openapi-loader.ts
64+
Layer 2 (transform) openapi-to-commands.ts (uses Profile), command-search.ts (uses CliCommand + bm25)
65+
Layer 3 (entry) cli.ts (uses everything above; only this layer talks to yargs/axios/process)
66+
```
67+
68+
Lower layers must not import from higher layers. New behavior should live in the lowest layer where it makes sense - prefer adding to Layer 2 over expanding `cli.ts`.
69+
70+
## Repository layout
71+
72+
- `src/` - production code (see components above).
73+
- `tests/` - Jest test files (`*.test.ts`), fixtures under `tests/fixtures/`, recorded results under `tests/results/`.
74+
- `examples/skill-ocli-api.md` - example Claude Code skill describing the agent workflow.
75+
- `skills/ocli-api/SKILL.md` - portable OpenClaw skill.
76+
- `benchmarks/benchmark.ts` - token-overhead comparison (MCP variants vs CLI).
77+
- `scripts/generate-version.js` - writes `src/version.ts` before build.
78+
- `.ocli/` - working dir at runtime (not part of source). Never committed.
79+
- `dist/` - `tsc` build output.
80+
81+
## When extending the spec parser
82+
83+
Real-world OpenAPI/Swagger documents drift from any single example. Before changing `openapi-to-commands.ts` or `openapi-loader.ts`:
84+
85+
- Add a minimal fixture under `tests/fixtures/` that reproduces the case (don't hand-edit `box-api-yaml.test.ts` or `github-api.test.ts` fixtures - those are real specs).
86+
- Cover both OAS 3 (`requestBody`, `components/schemas`) and Swagger 2 (`body`/`formData`, `definitions`) when the change affects request building.
87+
- Mention the new spec feature in the README "Broader spec support" or "Better request generation" section.

.claude/rules/code-style.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
paths:
3+
- "**/*.ts"
4+
---
5+
6+
# Code style (TypeScript)
7+
8+
Applies to every `.ts` file in `src/` and `tests/`.
9+
10+
## General
11+
12+
1. All code comments and identifiers in English. Documentation files in English.
13+
2. New files end with a single trailing newline.
14+
3. Straight double quotes `"` for string literals. Regular hyphen `-`, not em-dashes, in any English prose inside code or docs.
15+
4. Default to no comments. Add a comment only when the **why** is non-obvious (workaround, hidden constraint, subtle invariant). Identifiers should carry intent.
16+
17+
## TypeScript
18+
19+
- `strict: true` in `tsconfig.json`. Do not weaken it locally.
20+
- Exported functions and public APIs declare explicit parameter and return types. Local variables may use inference.
21+
- Use `interface` for reusable object shapes, `type` for unions, intersections, and mapped types.
22+
- Avoid `any`. When unavoidable, scope it as narrowly as possible and annotate `// eslint-disable-next-line @typescript-eslint/no-explicit-any` with a one-line reason (see `cli.ts:HttpClient` for the canonical example).
23+
- Prefer `unknown` over `any` at module boundaries; narrow with type guards.
24+
25+
## File structure
26+
27+
1. Imports first - Node built-ins (`path`, `fs`), then third-party (`axios`, `yargs`, `js-yaml`, `zod`, `ini`), then local relative imports.
28+
2. Types and interfaces.
29+
3. Module-level constants.
30+
4. Functions and classes.
31+
5. `export` last when it improves readability; `export class` / `export function` inline is also fine.
32+
33+
## Naming
34+
35+
- Classes - `PascalCase` (`ProfileStore`, `OpenapiLoader`, `CommandSearch`).
36+
- Functions and methods - `camelCase` (`loadSpec`, `buildCommands`, `selectProfile`).
37+
- Interfaces and type aliases - `PascalCase` (`Profile`, `CliCommand`, `HttpClient`).
38+
- Constants - `UPPER_SNAKE_CASE` for environment-style globals, otherwise meaningful `camelCase`.
39+
- Files - `kebab-case.ts` matching the dominant exported type (`profile-store.ts` exports `ProfileStore`).
40+
41+
## Error handling
42+
43+
- Throw `Error` subclasses with informative messages; never throw strings.
44+
- At the CLI boundary (`cli.ts`), catch and translate to a user-friendly message + non-zero exit code. Inner layers should let exceptions propagate.
45+
- For external input (HTTP responses, parsed YAML/JSON), validate with `zod` schemas before consuming.
46+
47+
## Async
48+
49+
- Prefer `async/await`. Avoid `.then()` chains in new code.
50+
- Don't fire-and-forget Promises; always `await` or explicitly handle.
51+
52+
## Testing-facing affordances
53+
54+
When a module performs I/O, accept the dependency via a constructor option (`fs`, `httpClient`, `stdout`). This is how `OpenapiLoader`, `ProfileStore`, and the `run()` entry in `cli.ts` are structured; follow the same pattern in new modules.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
paths:
3+
- "src/**/*.ts"
4+
---
5+
6+
# Implementation order (one unit at a time)
7+
8+
Applies when adding or extending modules under `src/`.
9+
10+
## Layer order (lower first)
11+
12+
The architecture in [architecture.md](architecture.md) defines four layers. New behavior should be added at the lowest layer it can live in, then composed upward:
13+
14+
1. **Layer 0 - pure** (`bm25.ts`, type-only files): no I/O, no Node built-ins beyond `Buffer`/`URL`/etc.
15+
2. **Layer 1 - I/O wrappers** (`config.ts`, `profile-store.ts`, `openapi-loader.ts`): touch `fs`, network, or env.
16+
3. **Layer 2 - transform** (`openapi-to-commands.ts`, `command-search.ts`): combine Layer 0 + Layer 1 outputs into the CLI command model.
17+
4. **Layer 3 - entry** (`cli.ts`): wire everything together for yargs and axios.
18+
19+
Forbidden: Layer N importing from Layer M when M > N. If a Layer 1 module suddenly needs a Layer 2 type, that is a sign the type belongs lower.
20+
21+
## Steps for a new module or class
22+
23+
1. Decide the layer using [architecture.md](architecture.md).
24+
2. Write a failing test in `tests/<module>.test.ts` that describes the smallest useful behavior (see [testing.md](testing.md) and [workflow.md](workflow.md)).
25+
3. Add the minimum implementation in `src/<module>.ts`. Follow [code-style.md](code-style.md) for types, naming, and constructor-injected I/O.
26+
4. Make the test pass.
27+
5. Run `npm test` to confirm no regressions, then `npm run build` to confirm `tsc` is clean.
28+
6. Only **then** integrate the new module into the layer above (typically `cli.ts`), guarded by its own test.
29+
30+
## Forbidden
31+
32+
- Implementing two unrelated modules in one step before either has tests.
33+
- Wiring a new module into `cli.ts` before its own tests pass.
34+
- Adding optional fields to `Profile`, `CliCommand`, or `CliCommandOption` without a test that exercises the new field.
35+
- Editing real-spec fixtures (`github-api.*`, `box-api-yaml.*`) to "make tests pass" - those represent contracts.
36+
37+
## Allowed
38+
39+
- Stub or fake dependencies (mock `HttpClient`, in-memory `fs`) while a lower layer is incomplete, provided the stub matches the documented contract.
40+
- Refactor a passing module to a cleaner shape after the test suite stays green.
41+
- Extending an existing Layer 2 module with a new transformation, as long as it is covered by a new test and does not import upward.

.claude/rules/testing.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
paths:
3+
- "tests/**/*.ts"
4+
---
5+
6+
# Test conventions (Jest)
7+
8+
Applies when editing or adding files under `tests/`.
9+
10+
## Layout
11+
12+
- All tests live in `tests/` alongside the matching `src/` module.
13+
- Test files: `tests/<module>.test.ts` (one per `src/<module>.ts`).
14+
- Shared inputs in `tests/fixtures/` (OpenAPI/Swagger documents, sample profiles). Recorded outputs in `tests/results/`.
15+
- `github-api.test.ts` and `box-api-yaml.test.ts` are large real-spec regression tests - do not hand-edit their fixtures.
16+
17+
## Structure
18+
19+
- Group with `describe(<moduleName>, ...)`; one nested `describe` per public method or scenario.
20+
- Test names: `it("does X when Y", ...)` - describe behavior, not implementation.
21+
- Arrange / Act / Assert order inside each `it`. Blank lines between the three sections are encouraged.
22+
23+
## Isolation
24+
25+
- Each `it` is independent. Use `beforeEach`/`afterEach` for setup and teardown, not module-level mutable state.
26+
- Mock `fs` and `axios` (`HttpClient`) through the constructor options the modules expose. Do **not** monkey-patch the real `fs`/`axios` modules in tests.
27+
- For `.ocli/` fixtures, use `fs.mkdtempSync(os.tmpdir() + "/...")` and clean up in `afterEach`.
28+
29+
## Running
30+
31+
```bash
32+
npm test # full Jest suite
33+
npx jest tests/<file>.test.ts # one file
34+
npx jest tests/<file>.test.ts -t "X" # one test by name
35+
```
36+
37+
## What to assert
38+
39+
- Public behavior visible at the module boundary - return values, written files, requests issued via the mocked `HttpClient`, captured `stdout`.
40+
- For BM25 / `command-search` - assert ranking order and matched commands, not internal scores.
41+
- For `openapi-to-commands` - assert the resulting `CliCommand[]` structure (names, options, body schema), not intermediate spec normalization.
42+
43+
## What not to assert
44+
45+
- Internal helper signatures, private state, exact log strings - those are implementation details.
46+
- Floating-point BM25 scores beyond ranking order.
47+
48+
## Fixtures
49+
50+
- Add new spec fixtures only when an existing one cannot reproduce the case. Keep them minimal: one path, one operation, only the fields needed for the test.
51+
- For tests covering spec features (OAS 3 `requestBody`, Swagger 2 `formData`, multi-file `$ref`, header/cookie params), name the fixture after the feature, e.g. `tests/fixtures/swagger2-formdata.yaml`.

.claude/rules/workflow.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Workflow (TDD, layered, docs-aware)
2+
3+
This file has no `paths:` frontmatter, so it loads at session start (same priority as `CLAUDE.md`).
4+
5+
`openapi-to-cli` (`ocli`) is a TypeScript CLI that turns OpenAPI/Swagger specs into runtime commands. The user-facing surface is the `ocli` binary, `.ocli/` config dir, and `profiles.ini`. Tests live next to source in `tests/` and run with Jest. Behavior is specified by tests; the README documents the public CLI contract.
6+
7+
## New features (TDD)
8+
9+
When the user asks for a new feature (words like "feature", "add", "implement", "фича", "добавить"), follow this order. Do not skip steps.
10+
11+
1. **Plan**: outline the steps for the change (modules to touch, tests to add). Use a task list if it spans more than 2-3 steps.
12+
2. **Failing test first**: add or extend a test in `tests/<module>.test.ts`. The test must describe the new behavior in `describe`/`it` and **fail** for the right reason.
13+
3. **Confirm red**: run only that test: `npx jest tests/<module>.test.ts -t "<title>"`. Confirm it fails as expected.
14+
4. **Implement**: write the minimum code in `src/` that makes the test pass. Follow [architecture.md](architecture.md) and [code-style.md](code-style.md). When adding new classes, follow [implementation-order.md](implementation-order.md) - lower layer first.
15+
5. **Confirm green**: re-run the same test; it must pass.
16+
6. **Full suite**: `npm test`. All tests must be green. Fix regressions before moving on.
17+
7. **Build check**: `npm run build` to confirm `tsc` is clean (no type errors).
18+
8. **Docs**: update `README.md` whenever any of the following change: CLI flags, command names, profile fields, `.ocli/` layout, BM25 search behavior, supported OpenAPI/Swagger features, or the benchmark numbers. If you changed observable CLI output (`--help`, error messages, exit codes), update the relevant section of the README. The `examples/skill-ocli-api.md` and `skills/ocli-api/SKILL.md` must stay aligned with the documented agent workflow.
19+
9. **Changelog**: add an entry to `CHANGELOG.md` for any user-visible change.
20+
10. **Report**: brief summary of files touched, tests added, suite result.
21+
22+
## Bug fixes (TDD)
23+
24+
When the user reports a bug (words like "bug", "fix", "ошибка", "баг", "исправить"):
25+
26+
1. **Plan** the fix.
27+
2. **Reproduction test**: add a test in `tests/<module>.test.ts` that reproduces the bug. It must **fail** on the broken code for the right reason.
28+
3. **Confirm red**: run only that test and confirm it fails.
29+
4. **Fix**: minimal code change in `src/` to make the test pass; respect existing module boundaries.
30+
5. **Confirm green**: re-run the regression test.
31+
6. **Full suite**: `npm test`. All tests green.
32+
7. **Build check**: `npm run build`.
33+
8. **Docs**: update `README.md` if the bug affected documented behavior; add a `CHANGELOG.md` entry.
34+
9. **Report**: what was broken, what changed, suite result.
35+
36+
## Before the final answer
37+
38+
- `npm test` is green - **always**.
39+
- `npm run build` is clean.
40+
- `README.md` and `CHANGELOG.md` reflect any user-visible change.
41+
- Report what changed, which tests were added, and the suite result.
42+
43+
## Rules sync (Cursor <-> Claude)
44+
45+
`.claude/rules/*.md` and `.cursor/rules/*.mdc` cover the same topics and must stay aligned. **Any change to a rule in one location must be mirrored to the other in the same change**, no exceptions.
46+
47+
Mapping:
48+
49+
| Topic | Claude | Cursor |
50+
|-------|--------|--------|
51+
| Workflow | `.claude/rules/workflow.md` | `.cursor/rules/workflow.mdc` |
52+
| Architecture | `.claude/rules/architecture.md` | `.cursor/rules/architecture.mdc` |
53+
| Code style | `.claude/rules/code-style.md` | `.cursor/rules/code-style.mdc` |
54+
| Testing | `.claude/rules/testing.md` | `.cursor/rules/testing.mdc` |
55+
| Implementation order | `.claude/rules/implementation-order.md` | `.cursor/rules/implementation-order.mdc` (create if absent) |
56+
57+
When propagating, translate the frontmatter:
58+
59+
- Claude `paths: ["src/**/*.ts"]` -> Cursor `globs: "src/**/*.ts"` + `alwaysApply: true` (or `false` for optional topics like `implementation-order`).
60+
- Claude rule without frontmatter (loaded every session, e.g. `workflow.md`) -> Cursor `alwaysApply: true` with no `globs`.
61+
- Replace cross-rule links: Claude `[architecture.md](architecture.md)` -> Cursor `@architecture.mdc`.
62+
63+
Body content stays identical. If the change is Cursor-only or Claude-only (very rare - e.g. tool-specific quirk), state that explicitly in the file as "Tool-specific:" and skip mirroring for that section only.
64+
65+
## Relationship to other rules
66+
67+
- [architecture.md](architecture.md) is loaded under `src/**` - use it when picking modules and dependency direction.
68+
- [implementation-order.md](implementation-order.md) is loaded under `src/**` - use it to decide layer order when adding new classes.
69+
- [code-style.md](code-style.md) is loaded for all `.ts` files - applies to both `src/` and `tests/`.
70+
- [testing.md](testing.md) is loaded under `tests/` - applies when writing or editing tests.

.cursor/rules/workflow.mdc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,26 @@ When fixing a bug:
4141
npm test
4242
```
4343

44+
### Rules sync (Cursor <-> Claude)
45+
46+
`.cursor/rules/*.mdc` and `.claude/rules/*.md` cover the same topics and must stay aligned. **Any change to a rule in one location must be mirrored to the other in the same change**, no exceptions.
47+
48+
Mapping:
49+
50+
| Topic | Cursor | Claude |
51+
|-------|--------|--------|
52+
| Workflow | `.cursor/rules/workflow.mdc` | `.claude/rules/workflow.md` |
53+
| Architecture | `.cursor/rules/architecture.mdc` | `.claude/rules/architecture.md` |
54+
| Code style | `.cursor/rules/code-style.mdc` | `.claude/rules/code-style.md` |
55+
| Testing | `.cursor/rules/testing.mdc` | `.claude/rules/testing.md` |
56+
| Implementation order | `.cursor/rules/implementation-order.mdc` (create if absent) | `.claude/rules/implementation-order.md` |
57+
58+
When propagating, translate the frontmatter:
59+
60+
- Cursor `globs: "src/**/*.ts"` + `alwaysApply: true` -> Claude `paths: ["src/**/*.ts"]`.
61+
- Cursor `alwaysApply: true` with no `globs` -> Claude rule without frontmatter (loaded every session).
62+
- Cursor `alwaysApply: false` (optional/topic rule) -> Claude `paths:` narrowed to the relevant tree.
63+
- Replace cross-rule links: Cursor `@architecture.mdc` -> Claude `[architecture.md](architecture.md)`.
64+
65+
Body content stays identical. If the change is Cursor-only or Claude-only (very rare - e.g. tool-specific quirk), state that explicitly in the file as "Tool-specific:" and skip mirroring for that section only.
66+

0 commit comments

Comments
 (0)