diff --git a/.yarnrc.yml b/.yarnrc.yml index c6e883c..dc61281 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,2 +1,6 @@ -nodeLinker: node-modules +approvedGitRepositories: + - "**" + +enableScripts: true +nodeLinker: node-modules diff --git a/CLAUDE.md b/CLAUDE.md index e7c345a..698ab06 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,4 +10,4 @@ Checks with `yarn lint` and `yarn test`. - **[guide.md](docs/guide.md)** - High level guide - **[examples.md](../examples.md)** - Practical usage examples for all functions -- **[functional.md](docs/functional.md)** - Reference docs +- **[reference.md](docs/reference.md)** - Reference docs diff --git a/README.md b/README.md index c4dccc0..6409fab 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,29 @@ export default [ ] ``` +## AI & Agentic Development + +Pipelean is "Agent-Ready." It ships with built-in **Skills** and an **Agent Persona** to help AI assistants (like Claude, Gemini CLI, or Cursor) write better code using this library. + +### 1. Install Skills + +The easiest way to install the skills is using the Vercel [agent-skills](https://github.com/vercel-labs/skills) CLI: + +```sh +npx skills add https://github.com/ildella/pipelean/tree/refactor-skills-agent/skills +``` + +This will install: +- `pipelean-core` +- `pipelean-functional-programming` + +### 2. (Experimental) using [skills-npm](https://github.com/antfu/skills-npm/) + +```sh +yarn add -D skills-npm +yarn skills-npm +``` + ## Documentation * [Architecture](docs/architecture.md) : The philosophy and design principles. diff --git a/SKILL.md b/SKILL.md deleted file mode 100644 index 44ee8cc..0000000 --- a/SKILL.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: pipelean -description: "Use for sequential async step chaining, retry logic, and named error strategies (failFast/failLate/skip). Triggers on: pipelean and series(), scan(), filter(), pipe(), retry(), tryCatch(), chaining dependent async steps where each result feeds the next, batch processing with per-item error collection, retrying flaky operations with delays, or any question about pipelean by name." -type: project - ---- - -# Pipelean Library - -A pragmatic library for sequential async operations with first-class error strategies. - -## References - - * [Guide](docs/guide.md): Core concepts - * [Reference](docs/reference.md): Reference docs for each function - * [Examples](docs/examples.md): Practical usage examples for all functions - * [Patterns](docs/patterns.md): Practical usage patterns diff --git a/install-skill.sh b/install-skill.sh deleted file mode 100755 index c6782ab..0000000 --- a/install-skill.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -mkdir -p ~/.claude/skills/pipelean/ -cp SKILL.md ~/.claude/skills/pipelean/ -cp -r docs ~/.claude/skills/pipelean/ diff --git a/package.json b/package.json index 7450398..075e33d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ }, "files": [ "src", - "SKILL.md", + "skills", + "pipelean-agent.md", "docs" ], "scripts": { @@ -40,16 +41,16 @@ "test": "vitest", "coverage": "vitest run --coverage" }, - "packageManager": "yarn@4.13.0+sha512.5c20ba010c99815433e5c8453112165e673f1c7948d8d2b267f4b5e52097538658388ebc9f9580656d9b75c5cc996f990f611f99304a2197d4c56d21eea370e7", + "packageManager": "yarn@4.14.1+sha512.64df448055b2d37ba269d7db535a469b8da93f8ef1140c25fd7a83c00a8fbaacb214ca0e02553b92a2c54cef78bb67d0b4817fab02001df0e24fac0faccc3b42", "devDependencies": { - "@eslint/compat": "2.0.3", + "@eslint/compat": "2.0.5", "@eslint/js": "9.39.4", "@stylistic/eslint-plugin-js": "3.1.0", - "@vitest/eslint-plugin": "1.6.14", + "@vitest/eslint-plugin": "1.6.16", "eslint": "9.39.4", "eslint-nostandard": "0.6.0", "globals": "15.15.0", - "vitest": "4.1.2" + "vitest": "4.1.5" }, "stableVersion": "0.5.0" } diff --git a/pipelean-agent.md b/pipelean-agent.md new file mode 100644 index 0000000..62d467e --- /dev/null +++ b/pipelean-agent.md @@ -0,0 +1,25 @@ +--- +name: Pipelean Architect +description: "Expert agent for pragmatic async functional programming, architecture, and enforcing Pipelean standards." +color: blue +skills: + - modern-javascript-patterns + - architecture-patterns + - pipelean-core + - pipelean-functional-programming +--- + +# Pipelean Architect + +You are the Pipelean Library expert and a Senior Functional Architect. + +Your goal is to build clean, predictable, and pragmatic asynchronous workflows using the Pipelean standard, and to enforce high-quality architectural patterns. + +## Your Workflow +1. When designing new features, always prioritize functional composition (`pipe`) over imperative nesting. +2. When iterating over data, use `series` or `scan`. Never use `forEach` or `reduce`. +3. Handle errors explicitly using Pipelean's error strategies (`failFast`, `failLate`, `collect`, `skip`) rather than scattering `try/catch` blocks randomly. +4. Utilize your `modern-javascript-patterns` and `architecture-patterns` skills to ensure the code remains idiomatic and loosely coupled. +5. If you are unsure about the usage of a specific Pipelean function, invoke your `pipelean-core` skill or refer to the local `docs/reference.md` file. + +You do NOT guess the library syntax. You consult the skills and documentation. diff --git a/skills/core/SKILL.md b/skills/core/SKILL.md new file mode 100644 index 0000000..509823a --- /dev/null +++ b/skills/core/SKILL.md @@ -0,0 +1,30 @@ +--- +name: "Pipelean Core" +description: "Pipelean core functionalities for iteration, composition and error management for pure FP in pure Javascript. Use this skill when you need to perform sequential async operations, control flow, error handling, or batch processing using the `pipelean` library" +--- + +## Available Functions + +1. **`series(items, fn, options)`**: Stateless sequential execution. + - Runs `fn` on each item one by one. + - `options`: `{ strategy: failFast | failLate | collect | skip, pause: ms, pauseOnErrors: boolean }` + - Returns: `{ results, errors, failure }` +2. **`scan(items, scannerFn, initialValue)`**: Stateful sequential transformation. + - `scannerFn(acc, item, index)` + - Returns: `{ results, errors, failure }` +3. **`filter(predicate, items, options)`**: Stateless selection. + - Predicate returns truthy to keep, falsy to drop. + - Returns original items where predicate matched in `results`. +4. **`pipe(...fns)`**: Vertical composition. + - Chains functions left-to-right. + - If any step returns `undefined`, remaining steps are skipped (short-circuit). +5. **`retry(fn, { attempts, delay })`**: Configurable retry logic. +6. **`tryCatch(fn, { onStart, onSuccess, onError, onFinally, rethrow })`**: Single function lifecycle hooks. + +## Error Strategies +- **`failFast`**: Stops immediately on first error. `failure: {item, error}`. +- **`collect`**: Continues processing, collects all errors. `failure: null`. (Default for series) +- **`failLate`**: Collects all errors, sets `failure: true` at the end if any occurred. +- **`skip`**: Ignores errors entirely. + +**Documentation**: Read full examples in [docs/reference.md](../../docs/reference.md) (or [online](https://github.com/ildella/pipelean/blob/master/docs/reference.md)). diff --git a/skills/fp-standards/SKILL.md b/skills/fp-standards/SKILL.md new file mode 100644 index 0000000..70f7d8d --- /dev/null +++ b/skills/fp-standards/SKILL.md @@ -0,0 +1,17 @@ +--- +name: "Pipelean Functional Programming" +description: "General Functional Programming principles and patterns. Use this skill to enforce Pipelean's pragmatic functional programming standards and architectural principles on any codebase." +--- + +## Core Philosophy +1. **No swallowed errors**: Do not swallow errors or inconsistencies (`undefined`, `null`) during array sync/async operations. +2. **Pragmatism over Purity**: We prefer explicit results over lazy iterables. Execution is eager. +3. **No Boilerplate**: Avoid writing repetitive `try/catch` or manual iteration loops. Use `series`, `pipe`, or `scan`. + +## Coding Rules +- **No `Array.prototype.forEach`**: ForEach swallows promises and doesn't handle async errors correctly. Use `series` instead. +- **No `Array.prototype.reduce`**: Reduce is often unreadable and mixes state with iteration. Use `scan` for stateful accumulation. +- **Eager Execution**: Do not yield generators or use lazy execution unless strictly required by a specific framework boundary. Get a structured report `{ results, errors, failure }` back immediately. +- **Undefined Short-Circuit**: When composing operations (via `pipe`), returning `undefined` signals dropping the item (selection/filtering). + +**Documentation**: Read the full architectural philosophy in [docs/architecture.md](../../docs/architecture.md) (or [online](https://github.com/ildella/pipelean/blob/master/docs/architecture.md)). diff --git a/yarn.lock b/yarn.lock index 388b783..37959a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # Manual changes might be lost - proceed with caution! __metadata: - version: 8 + version: 9 cacheKey: 10c0 "@emnapi/core@npm:^1.7.1": @@ -51,17 +51,17 @@ __metadata: languageName: node linkType: hard -"@eslint/compat@npm:2.0.3": - version: 2.0.3 - resolution: "@eslint/compat@npm:2.0.3" +"@eslint/compat@npm:2.0.5": + version: 2.0.5 + resolution: "@eslint/compat@npm:2.0.5" dependencies: - "@eslint/core": "npm:^1.1.1" + "@eslint/core": "npm:^1.2.1" peerDependencies: eslint: ^8.40 || 9 || 10 peerDependenciesMeta: eslint: optional: true - checksum: 10c0/e7b1a1bdf366759f9a7c3f09fe35cdd0f45208a6ca789be1357895869e546d0c3185ec92b29996141fdd1173103c3408a81ba2ecb86219bd15671ae65af6595b + checksum: 10c0/c6e16c5bd826535dc84b6dfd4cfa8079ac564f6dc614b164e2f2e708e940d6efa9f3754fa6ddaace04b43d296c190aabbad4231c074f6269afab88d7e7b005a8 languageName: node linkType: hard @@ -94,12 +94,12 @@ __metadata: languageName: node linkType: hard -"@eslint/core@npm:^1.1.1": - version: 1.1.1 - resolution: "@eslint/core@npm:1.1.1" +"@eslint/core@npm:^1.2.1": + version: 1.2.1 + resolution: "@eslint/core@npm:1.2.1" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/129c654c78afc1f6d61dccb0ce841be667f09f052f7d5642614b6ba5eeebd579ca6cc336d7b750d88625e61f7aad22fdd62bf83847fbfc10cc3e58cfe6c5072e + checksum: 10c0/10979b40588ecfef771fcb5013a542a35fb30692cc95a65f3481b0b36fbd89f5679efeb30d57f4eed35203d859aabace2a620177d6c536f71b299a1af2f3398f languageName: node linkType: hard @@ -517,9 +517,9 @@ __metadata: languageName: node linkType: hard -"@vitest/eslint-plugin@npm:1.6.14": - version: 1.6.14 - resolution: "@vitest/eslint-plugin@npm:1.6.14" +"@vitest/eslint-plugin@npm:1.6.16": + version: 1.6.16 + resolution: "@vitest/eslint-plugin@npm:1.6.16" dependencies: "@typescript-eslint/scope-manager": "npm:^8.58.0" "@typescript-eslint/utils": "npm:^8.58.0" @@ -535,29 +535,29 @@ __metadata: optional: true vitest: optional: true - checksum: 10c0/4721f554cab1c8bfa6d0df7b350e54223ecfd285ba3f51be5db2dafaa6147b26400dd80840114019bd80f584fed6e30180ba70e4b044dbfa7b909b283d241d8e + checksum: 10c0/b2871ce12b831ea1e1ff8cf01d978d0746aaeac951830880ad966ad2783de4fbe835a1c74c44f5bccca73ca6007174cd0434c2725269cfafdcc6fbb43d4da495 languageName: node linkType: hard -"@vitest/expect@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/expect@npm:4.1.2" +"@vitest/expect@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/expect@npm:4.1.5" dependencies: "@standard-schema/spec": "npm:^1.1.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.1.2" - "@vitest/utils": "npm:4.1.2" + "@vitest/spy": "npm:4.1.5" + "@vitest/utils": "npm:4.1.5" chai: "npm:^6.2.2" tinyrainbow: "npm:^3.1.0" - checksum: 10c0/e238c833b5555d31b074545807956d5e874a1ef725525ecc99f1885b71b230b2127d40d8d142a7253666b8565d5806723853e85e0e99265520ec7506fdc5890c + checksum: 10c0/5184682304db471aa20024c1154210ad3d6d590afb61646201ce1a15297259f9a35f92f8fad4435bc8a82135e307ddd27c8495f72417d72d9aa139eb281d9e06 languageName: node linkType: hard -"@vitest/mocker@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/mocker@npm:4.1.2" +"@vitest/mocker@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/mocker@npm:4.1.5" dependencies: - "@vitest/spy": "npm:4.1.2" + "@vitest/spy": "npm:4.1.5" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.21" peerDependencies: @@ -568,56 +568,56 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/f23094f3c7e1e5af42e6a468f0815c1ecdcab85cb3a56ab6f3f214a9808a40271467d4352cae972482b9738cc31c62c7312d8b0da227d6ea03d2b3aacb8d385f + checksum: 10c0/bcfe97700476130933c7ea33fa670c8d2768a81de5325ce407f901e55c2f66cabbb88a7b6cffb46ddf33dff7d8fc209d769fb298f568e310fbeead9b36f6fdb9 languageName: node linkType: hard -"@vitest/pretty-format@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/pretty-format@npm:4.1.2" +"@vitest/pretty-format@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/pretty-format@npm:4.1.5" dependencies: tinyrainbow: "npm:^3.1.0" - checksum: 10c0/6f57519c707e6a3d1ff8630ca87ce78fda9bf7bb33f6e4a0c775a8b510f2a6cee109849e2cdb736b0280681c567bd03e4cff724cbf0962950c9ff81377f0b2bc + checksum: 10c0/42b5e9b75e87c0a884d36bee364e2d07ee45e96f413377737a74993e077d90c3a12aa36743855aee5e4e28b78fae20e3e6de5eef8d5344b9aba2bc1e1d5537a1 languageName: node linkType: hard -"@vitest/runner@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/runner@npm:4.1.2" +"@vitest/runner@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/runner@npm:4.1.5" dependencies: - "@vitest/utils": "npm:4.1.2" + "@vitest/utils": "npm:4.1.5" pathe: "npm:^2.0.3" - checksum: 10c0/35654a87bd27983443adc24d68529d624f7d70e0386176741dc5bcc4188b86a70af2c512405d7e97aa45c16d83e1c8566c1f99c8440430f95557275f18612d21 + checksum: 10c0/6a03b313a121155f6dd9e32eeb103c0e12440f586bc4ba1f0d77444e44c6df4652a44443718552037463115635b8378e11f35902d90ce1326f77743219fca056 languageName: node linkType: hard -"@vitest/snapshot@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/snapshot@npm:4.1.2" +"@vitest/snapshot@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/snapshot@npm:4.1.5" dependencies: - "@vitest/pretty-format": "npm:4.1.2" - "@vitest/utils": "npm:4.1.2" + "@vitest/pretty-format": "npm:4.1.5" + "@vitest/utils": "npm:4.1.5" magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/6d20e92386937afddbc81344211e554b83a559e20fb10c1deb0b1c3532994dc9fc62d816706ac835bdb737eb1ab02e9c0bc9de80dd8316060e1e0aaa447ba48f + checksum: 10c0/e11bf50d06702331290750a40eaef86078c108df3cd9a52bb1be7b84250048790163f36827525be6a383a4bb1994fc35e6d0c24239a41688b0bb68a1d15d172f languageName: node linkType: hard -"@vitest/spy@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/spy@npm:4.1.2" - checksum: 10c0/2b5888d536d3e2083c5f8939763e6d780c2c03cc60e1ab45f9d04eacf14467acb9724cae1c4778e4c06426d49d04517e190122882953054a4b13fda44780bb14 +"@vitest/spy@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/spy@npm:4.1.5" + checksum: 10c0/fda6b1ee0a2fec1a152d8041aba7a79744c3876863b244d1ed406d02b36e8ccc997edb2e3963d1027d728d3dc5a33813e11bef53a0a14fc7de4de5e721d0f591 languageName: node linkType: hard -"@vitest/utils@npm:4.1.2": - version: 4.1.2 - resolution: "@vitest/utils@npm:4.1.2" +"@vitest/utils@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/utils@npm:4.1.5" dependencies: - "@vitest/pretty-format": "npm:4.1.2" + "@vitest/pretty-format": "npm:4.1.5" convert-source-map: "npm:^2.0.0" tinyrainbow: "npm:^3.1.0" - checksum: 10c0/d96475e0703b6e5208c6c0f570c1235278cbac3f3913a9aa4203a3e617c9eaca85a184bfd5d13cf366b84754df787ab8bc85242c5e0c63105ee7176c186a2136 + checksum: 10c0/72409717e68018e5fe42fa173cc4eff6def8c35bd52013f86ddb414cd28d73fcc425ac62968e01a52371b3fd5a7a775536283d2f1d64432753f628712a6a4908 languageName: node linkType: hard @@ -1810,14 +1810,14 @@ __metadata: version: 0.0.0-use.local resolution: "pipelean@workspace:." dependencies: - "@eslint/compat": "npm:2.0.3" + "@eslint/compat": "npm:2.0.5" "@eslint/js": "npm:9.39.4" "@stylistic/eslint-plugin-js": "npm:3.1.0" - "@vitest/eslint-plugin": "npm:1.6.14" + "@vitest/eslint-plugin": "npm:1.6.16" eslint: "npm:9.39.4" eslint-nostandard: "npm:0.6.0" globals: "npm:15.15.0" - vitest: "npm:4.1.2" + vitest: "npm:4.1.5" languageName: unknown linkType: soft @@ -2201,17 +2201,17 @@ __metadata: languageName: node linkType: hard -"vitest@npm:4.1.2": - version: 4.1.2 - resolution: "vitest@npm:4.1.2" - dependencies: - "@vitest/expect": "npm:4.1.2" - "@vitest/mocker": "npm:4.1.2" - "@vitest/pretty-format": "npm:4.1.2" - "@vitest/runner": "npm:4.1.2" - "@vitest/snapshot": "npm:4.1.2" - "@vitest/spy": "npm:4.1.2" - "@vitest/utils": "npm:4.1.2" +"vitest@npm:4.1.5": + version: 4.1.5 + resolution: "vitest@npm:4.1.5" + dependencies: + "@vitest/expect": "npm:4.1.5" + "@vitest/mocker": "npm:4.1.5" + "@vitest/pretty-format": "npm:4.1.5" + "@vitest/runner": "npm:4.1.5" + "@vitest/snapshot": "npm:4.1.5" + "@vitest/spy": "npm:4.1.5" + "@vitest/utils": "npm:4.1.5" es-module-lexer: "npm:^2.0.0" expect-type: "npm:^1.3.0" magic-string: "npm:^0.30.21" @@ -2229,10 +2229,12 @@ __metadata: "@edge-runtime/vm": "*" "@opentelemetry/api": ^1.9.0 "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.1.2 - "@vitest/browser-preview": 4.1.2 - "@vitest/browser-webdriverio": 4.1.2 - "@vitest/ui": 4.1.2 + "@vitest/browser-playwright": 4.1.5 + "@vitest/browser-preview": 4.1.5 + "@vitest/browser-webdriverio": 4.1.5 + "@vitest/coverage-istanbul": 4.1.5 + "@vitest/coverage-v8": 4.1.5 + "@vitest/ui": 4.1.5 happy-dom: "*" jsdom: "*" vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -2249,6 +2251,10 @@ __metadata: optional: true "@vitest/browser-webdriverio": optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true "@vitest/ui": optional: true happy-dom: @@ -2259,7 +2265,7 @@ __metadata: optional: false bin: vitest: vitest.mjs - checksum: 10c0/061fdd0319ba533c926b139b9377a7dbf91e63d815d86fe318a207bd19842b74ca6f6402ea61b26ed9d2924306bdb4d0b13f69c29e2a2a89b3b67602bcccb54c + checksum: 10c0/196eaf5e95b45a3f6d3001a2408d7dc6f146c29c873ed4e42e1ad4c9327122934fb3793a12b6ce3b7c25d355e738b20123acc0894ce30358c3370b15f4bd0865 languageName: node linkType: hard