├── .cursor
├── .gitignore
├── docs
└── tap-color-screenshot.png
├── match.js
├── vitest.js
├── match.d.ts
├── source
├── index.js
├── vitest.js
├── render-component.js
├── match.js
├── match-test.js
├── riteway.js
├── test.js
└── vitest.test.jsx
├── tea.yaml
├── render-component.js
├── ai
├── commands
│ ├── commit.md
│ ├── log.md
│ ├── plan.md
│ ├── task.md
│ ├── execute.md
│ ├── review.md
│ ├── discover.md
│ └── help.md
└── rules
│ ├── requirements.mdc
│ ├── commit.mdc
│ ├── ui.mdc
│ ├── frameworks
│ └── redux
│ │ ├── example.mdc
│ │ └── autodux.mdc
│ ├── log.mdc
│ ├── stack.mdc
│ ├── review.mdc
│ ├── please.mdc
│ ├── javascript
│ ├── javascript-io-network-effects.mdc
│ └── javascript.mdc
│ ├── agent-orchestrator.mdc
│ ├── productmanager.mdc
│ ├── tdd.mdc
│ └── task-creator.mdc
├── .travis.yml
├── render-component.d.ts
├── renovate.json
├── vitest.d.ts
├── activity-log.md
├── vitest.config.js
├── .release-it.json
├── .eslintrc.json
├── index.d.ts
├── LICENSE
├── eslint.config.js
├── plan.md
├── bin
├── riteway
└── riteway.test.js
├── package.json
├── tasks
├── 2025-01-27-context7-github-action-epic.md
└── archive
│ ├── 2025-09-27-release-script-improvement-epic.md
│ └── 2025-09-27-modernize-test-runner-epic.md
├── release.js
└── README.md
/.cursor:
--------------------------------------------------------------------------------
1 | ai
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .esm-cache/
3 |
--------------------------------------------------------------------------------
/docs/tap-color-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paralleldrive/riteway/HEAD/docs/tap-color-screenshot.png
--------------------------------------------------------------------------------
/match.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-global-assign
2 | require = require('esm')(module);
3 | module.exports = require('./source/match');
4 |
--------------------------------------------------------------------------------
/vitest.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-global-assign
2 | require = require('esm')(module);
3 | module.exports = require('./source/vitest');
--------------------------------------------------------------------------------
/match.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'riteway/match' {
2 | export default function match(
3 | text: string
4 | ): (pattern: string | RegExp) => string;
5 | }
6 |
--------------------------------------------------------------------------------
/source/index.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-global-assign
2 | require = require('esm')(module);
3 | module.exports = require('./riteway.js');
4 |
5 |
--------------------------------------------------------------------------------
/tea.yaml:
--------------------------------------------------------------------------------
1 | # https://tea.xyz/what-is-this-file
2 | ---
3 | version: 1.0.0
4 | codeOwners:
5 | - '0x85fb7e6Fc8FC092e0D279A48988840fa0090c3E5'
6 | quorum: 1
7 |
--------------------------------------------------------------------------------
/render-component.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-global-assign
2 | require = require('esm')(module);
3 | module.exports = require('./source/render-component');
4 |
--------------------------------------------------------------------------------
/ai/commands/commit.md:
--------------------------------------------------------------------------------
1 | ## 💾 Commit
2 |
3 | Use commit.mdc to commit the changes to the repository.
4 | Before beginning, read and respect the constraints in please.mdc.
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 18
4 | install:
5 | - npm install --legacy-peer-deps
6 | before_script:
7 | - export NODE_OPTIONS=–max_old_space_size=8192
--------------------------------------------------------------------------------
/ai/commands/log.md:
--------------------------------------------------------------------------------
1 | ## 📝 Log
2 |
3 | Use log.mdc to collect salient changes, and log them to the activity-log.md.
4 | Before beginning, read and respect the constraints in please.mdc.
5 |
--------------------------------------------------------------------------------
/ai/commands/plan.md:
--------------------------------------------------------------------------------
1 | ## 📋 Plan
2 |
3 | Review plan.md to identify priorities and suggest next steps to the user -d 10.
4 | Before beginning, read and respect the constraints in please.mdc.
5 |
--------------------------------------------------------------------------------
/source/vitest.js:
--------------------------------------------------------------------------------
1 | import { expect } from 'vitest';
2 |
3 | export const assert = ({ given, should, actual, expected }) => {
4 | expect(actual, `Given ${given}: should ${should}`).toStrictEqual(expected);
5 | };
6 |
--------------------------------------------------------------------------------
/render-component.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare module 'riteway/render-component' {
5 | export default function render(el: JSX.Element): cheerio.Root;
6 | }
7 |
--------------------------------------------------------------------------------
/source/render-component.js:
--------------------------------------------------------------------------------
1 | import reactDom from 'react-dom/server';
2 | import { load } from 'cheerio';
3 |
4 | const render = component =>
5 | load(reactDom.renderToStaticMarkup(component));
6 |
7 | export default render;
8 |
9 |
--------------------------------------------------------------------------------
/ai/commands/task.md:
--------------------------------------------------------------------------------
1 | ## ✅ Task
2 |
3 | Use the task creator to plan and execute a task epic.
4 |
5 | Constraints {
6 | Before beginning, read and respect the constraints in please.mdc.
7 | Remember to use the TDD process if asked to implement code.
8 | }
9 |
--------------------------------------------------------------------------------
/ai/commands/execute.md:
--------------------------------------------------------------------------------
1 | ## ⚙️ Execute Task/Epic
2 |
3 | Use the task creator to execute a task epic.
4 |
5 | Constraints {
6 | Before beginning, read and respect the constraints in please.mdc.
7 | Remember to use the TDD process if asked to implement code.
8 | }
9 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "automerge": true,
6 | "automergeType": "branch",
7 | "major": {
8 | "automerge": false
9 | },
10 | "schedule": "before 4am",
11 | "timezone": "America/Los_Angeles"
12 | }
13 |
--------------------------------------------------------------------------------
/ai/commands/review.md:
--------------------------------------------------------------------------------
1 | # 🔬 Code Review
2 |
3 | use review.mdc to conduct a thorough code review focusing on code quality, best practices, and adherence to project standards.
4 |
5 | Constraints {
6 | Before beginning, read and respect the constraints in please.mdc.
7 | }
8 |
--------------------------------------------------------------------------------
/ai/commands/discover.md:
--------------------------------------------------------------------------------
1 | ## 🔍 Discover
2 |
3 | Use productmanager.mdc to discover a user journey, user story, or feature.
4 |
5 | Constraints {
6 | Begin by reading the file and asking the user relevant questions to spark the discovery process.
7 | Before beginning, read and respect the constraints in please.mdc.
8 | }
9 |
--------------------------------------------------------------------------------
/vitest.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare module 'riteway/vitest' {
5 | interface Assertion {
6 | readonly given: string;
7 | readonly should: string;
8 | readonly actual: T;
9 | readonly expected: T;
10 | }
11 |
12 | export function assert(assertion: Assertion): void;
13 | }
--------------------------------------------------------------------------------
/ai/commands/help.md:
--------------------------------------------------------------------------------
1 | ## ❓ Help
2 |
3 | List commands from please.mdc and report them to the user.
4 |
5 | Constraints {
6 | Before beginning, read and respect the constraints in please.mdc.
7 | Keep the response extremely concise - essentially just the list of commands, their descriptions, and options, without offering trivial details or informing users of constraints.
8 | }
9 |
--------------------------------------------------------------------------------
/activity-log.md:
--------------------------------------------------------------------------------
1 | # Activity Log
2 |
3 | ## 2025-10-06
4 |
5 | - ✅ - Completed Modernize Test Runner Epic: Verified native ES module support working with dual test runner setup (31 core tests + 6 Vitest tests), updated documentation to reflect current JSX transpilation requirements
6 |
7 | ## 2025-09-27
8 |
9 | - 🚀 - Added complete AIDD (AI Driven Development) system with commands and rules
10 | - 🚀 - Enhanced automated release system with robust error handling and validation
11 |
--------------------------------------------------------------------------------
/vitest.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig({
4 | test: {
5 | exclude: [
6 | '**/node_modules/**',
7 | '**/dist/**',
8 | '**/cypress/**',
9 | '**/.{idea,git,cache,output,temp}/**',
10 | '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*',
11 | // Exclude bin/riteway.test.js as it uses Tape instead of Vitest
12 | '**/bin/riteway.test.js'
13 | ]
14 | }
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "git": {
3 | "commitMessage": "chore(release): v${version}",
4 | "tagName": "v${version}",
5 | "requireCleanWorkingDir": true,
6 | "requireBranch": ["main", "master", "release"]
7 | },
8 | "github": {
9 | "release": true,
10 | "releaseName": "v${version}",
11 | "autoGenerate": {
12 | "template": "compact"
13 | }
14 | },
15 | "npm": {
16 | "publish": true
17 | },
18 | "hooks": {
19 | "before:init": ["npm test"],
20 | "after:release": "echo 🎉 Successfully released ${name} v${version}"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ai/rules/requirements.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When writing functional requirements for a user story, use this guide for functional requirement specification
3 | alwaysApply: false
4 | ---
5 | # Functional requirements
6 |
7 | Act as a senior product manager to write functional requirements for a user story.
8 |
9 | type FunctionalRequirement = "Given $situation, should $jobToDo"
10 |
11 | Constraints {
12 | Focus on functional requirements to support the user journey.
13 | Avoid describing specific UI elements or interactions, instead, focus on the job the user wants to accomplish and the benefits we expect the user to achieve.
14 | }
15 |
--------------------------------------------------------------------------------
/source/match.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Take some text to search and returns a function
3 | * which takes a pattern and returns the matched text,
4 | * if found, or an empty string. The pattern can be a
5 | * string or regular expression.
6 | *
7 | * @param {string} text The text to search.
8 | * @returns {(pattern: string|Object) => string}
9 | */
10 | const match = text => pattern => {
11 | const RE = new RegExp(typeof pattern === 'string' ? escapeRegex(pattern) : pattern);
12 | const matched = text.match(RE);
13 | return matched ? matched[0] : '';
14 | };
15 |
16 | const escapeRegex = string =>
17 | string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
18 |
19 | export default match;
20 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:react/recommended"
10 | ],
11 | "parserOptions": {
12 | "sourceType": "module",
13 | "ecmaVersion": "2017",
14 | "ecmaFeatures": {
15 | "jsx": true
16 | }
17 | },
18 | "settings": {
19 | "react": {
20 | "version": "detect"
21 | }
22 | },
23 | "rules": {
24 | "indent": [
25 | "error",
26 | 2
27 | ],
28 | "linebreak-style": [
29 | "error",
30 | "unix"
31 | ],
32 | "quotes": [
33 | "error",
34 | "single"
35 | ],
36 | "semi": [
37 | "error",
38 | "always"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ai/rules/commit.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When committing code, use this guide for conventional commit format with proper message structure
3 | alwaysApply: false
4 | ---
5 | # Commit
6 |
7 | Act as a senior software engineer to commit changes to the repository in non-interactive modes ONLY, using the following template:
8 |
9 | "$type${[(scope)]}{[!]}: $description":where `[]` is optional and `!` is a breaking change
10 |
11 | Types: fix|feat|chore|docs|refactor|test|perf|build|ci|style|revert|$other
12 |
13 | If we haven't logged yet, use log.sudo to log changes before committing.
14 |
15 | Constraints {
16 | When committing, don't log about logging in the commit message.
17 | Use multiple -m flags, one for each log entry.
18 | Limit the first commit message line length to 50 characters.
19 | }
20 |
--------------------------------------------------------------------------------
/ai/rules/ui.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When building user interfaces and user experiences, use this guide for beautiful and friendly UI/UX design
3 | alwaysApply: false
4 | ---
5 |
6 | # UI/UX Engineer
7 |
8 | Act as a top-tier UI/UX designer with deep skills in user interface design, user experience design, aesthetics, extraordinarily good taste, an eye for detail, and a passion for building the most beautiful and friendly user interfaces and experiences. You are also a top tier motion designer, skilled in subtle but delightful and satisfying motion design for UX.
9 |
10 | When building UI components, please use the existing project design system and storybook components. Focus on creating intuitive, accessible, and visually appealing interfaces that enhance user experience.
11 |
12 | Skills [
13 | CSS
14 | HTML
15 | JavaScript
16 | React
17 | Animation
18 | Motion design
19 | Graphic design
20 | UI/UX design
21 | Accessibility
22 | Responsive design
23 | Design systems
24 | ]
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare module 'riteway' {
5 |
6 | export function Try(fn: (...args: U) => V, ...args: U): any | Promise
7 |
8 | export function createStream(opts: CreateStreamOptions): ReadableStream
9 |
10 | export const describe: DescribeFunction;
11 |
12 | interface DescribeFunction {
13 | (unit: string, testFunction: TestFunction): Promise
14 | only: (unit: string, testFunction: TestFunction) => Promise
15 | skip: (unit: string, testFunction: TestFunction) => Promise
16 | }
17 |
18 | type assert = (assertion: Assertion) => void
19 |
20 | type TestFunction = (assert: assert, end?: Function) => Promise
21 |
22 | interface Assertion {
23 | readonly given: any
24 | readonly should: string
25 | readonly actual: T
26 | readonly expected: T
27 | }
28 |
29 | interface CreateStreamOptions {
30 | readonly objectMode: boolean
31 | }
32 | }
--------------------------------------------------------------------------------
/ai/rules/frameworks/redux/example.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: Autodux usage example showing Todo App implementation in SudoLang
3 | alwaysApply: false
4 | ---
5 | ## AutoduxUsage Example: Todo App
6 |
7 | /*
8 | This is an example SudoLang source that can be transpiled to JavaScript using:
9 |
10 | ```
11 | TodoDux |> transpile(JavaScript)
12 | ```
13 |
14 | We recommend authoring all code in SudoLang and transpiling to JavaScript when you need to provide detailed specifications to AI agents.
15 | */
16 |
17 | Todo Item {
18 | id,
19 | text,
20 | isComplete,
21 | }
22 |
23 | createTodo({ text = '', id = createId(), isComplete = false } = {}) => ActionObject
24 | deleteTodo(id) => ActionObject
25 | toggleComplete(id) => ActionObject
26 |
27 | TodoDux {
28 | initialState = []
29 | slice = 'todo'
30 | actions = [createTodo, deleteTodo, toggleComplete]
31 | selectors = [getTodos, getIncompleteTodos, getCompleteTodos]
32 | mapStateToProps
33 | mapDispatchToProps
34 | connectedComponentName = TodoList
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Eric Elliott
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.
22 |
--------------------------------------------------------------------------------
/ai/rules/log.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When documenting changes, use this guide for creating structured change logs with emoji categorization
3 | alwaysApply: false
4 | ---
5 | # log
6 |
7 | Act as a senior software engineer to log changes to the repository using the following template:
8 |
9 | ```
10 | ## $date
11 |
12 | - $emoji - $change1
13 | - $emoji -$change2
14 | ```
15 |
16 | # Emojis
17 |
18 | Use the following emoji to represent the change:
19 |
20 | - 🚀 - new feature
21 | - 🐛 - bug fix
22 | - 📝 - documentation
23 | - 🔄 - refactor
24 | - 📦 - dependency update
25 | - 🎨 - design
26 | - 📱 - UI/UX
27 | - 📊 - analytics
28 | - 🔒 - security
29 |
30 | Constraints {
31 | Always use reverse chronological order.
32 | Add most recent changes to the top.
33 | Never log about logging. Avoid logging meta-work. Instead, log salient, user-impacting changes.
34 | }
35 |
36 |
37 | gitChanges() {
38 | git add .
39 | git --no-pager diff --cached
40 | }
41 |
42 | planChanges() {
43 | Check the plan diff to detect recently completed plan tasks.
44 | }
45 |
46 | detectChanges() {
47 | gitChanges |> planChanges |> logDetectedChanges
48 | }
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import react from 'eslint-plugin-react';
3 |
4 | export default [
5 | js.configs.recommended,
6 | {
7 | files: ['**/*.js', '**/*.jsx'],
8 | languageOptions: {
9 | ecmaVersion: 2017,
10 | sourceType: 'module',
11 | parserOptions: {
12 | ecmaFeatures: {
13 | jsx: true
14 | }
15 | },
16 | globals: {
17 | // Browser environment
18 | window: 'readonly',
19 | document: 'readonly',
20 | console: 'readonly',
21 | // CommonJS environment
22 | require: 'readonly',
23 | module: 'readonly',
24 | exports: 'readonly',
25 | global: 'readonly',
26 | process: 'readonly',
27 | // ES6 environment
28 | Promise: 'readonly',
29 | Set: 'readonly',
30 | Map: 'readonly',
31 | Symbol: 'readonly',
32 | // Node.js timer functions
33 | setTimeout: 'readonly',
34 | clearTimeout: 'readonly'
35 | }
36 | },
37 | plugins: {
38 | react
39 | },
40 | rules: Object.assign({}, react.configs.recommended.rules, {
41 | 'indent': ['error', 2],
42 | 'linebreak-style': ['error', 'unix'],
43 | 'quotes': ['error', 'single'],
44 | 'semi': ['error', 'always']
45 | }),
46 | settings: {
47 | react: {
48 | version: 'detect'
49 | }
50 | }
51 | }
52 | ];
53 |
--------------------------------------------------------------------------------
/source/match-test.js:
--------------------------------------------------------------------------------
1 | import { describe } from './riteway.js';
2 | import match from './match.js';
3 |
4 | describe('match', async assert => {
5 | {
6 | const given = 'some text to search and a pattern to match';
7 | const should = 'return the matched text';
8 |
9 | const textToSearch = 'Dialog Title
';
10 | const pattern = 'Dialog Title';
11 | const contains = match(textToSearch);
12 |
13 | assert({
14 | given,
15 | should,
16 | actual: contains(pattern),
17 | expected: pattern,
18 | });
19 | }
20 |
21 | {
22 | const given = 'some text with digits';
23 | const should = 'return the matched text';
24 |
25 | const textWithDigit = 'There are 4 cats
';
26 | const pattern = /\d+\s\w+/i;
27 | const contains = match(textWithDigit);
28 |
29 | assert({
30 | given,
31 | should,
32 | actual: contains(pattern),
33 | expected: '4 cats'
34 | });
35 | }
36 |
37 | {
38 | const given = 'some text that includes regex meta characters';
39 | const should = 'return the matched text';
40 |
41 | const textWithRegexMetaChar = 'Are there any cats?
';
42 | const pattern = 'Are there any cats?';
43 | const contains = match(textWithRegexMetaChar);
44 |
45 | assert({
46 | given,
47 | should,
48 | actual: contains(pattern),
49 | expected: 'Are there any cats?'
50 | });
51 | }
52 |
53 | });
54 |
--------------------------------------------------------------------------------
/plan.md:
--------------------------------------------------------------------------------
1 | # Riteway Project Plan
2 |
3 | ## Current Epics
4 |
5 | ### 📋 Context7 GitHub Action Integration Epic
6 | **Status**: 📋 PLANNING - Awaiting Approval
7 | **File**: [`tasks/2025-01-27-context7-github-action-epic.md`](./tasks/2025-01-27-context7-github-action-epic.md)
8 | **Goal**: Add Upsert Context7 GitHub Action to repository with automatic updates after successful release script runs
9 |
10 | ## Completed Epics
11 |
12 | ### ✅ Modernize Test Runner Epic
13 | **Status**: ✅ COMPLETED (2025-10-06)
14 | **File**: [`tasks/archive/2025-09-27-modernize-test-runner-epic.md`](./tasks/archive/2025-09-27-modernize-test-runner-epic.md)
15 | **Goal**: Modernize test runner to support native ES modules by separating JSX component tests from core Riteway functionality
16 | **Result**: Verified native ES module support working with dual test runner setup (31 core tests + 6 Vitest tests). Updated documentation to reflect current JSX transpilation requirements.
17 |
18 | ### ✅ Release Script Improvement Epic
19 | **Status**: ✅ COMPLETED (2025-09-27)
20 | **File**: [`tasks/archive/2025-09-27-release-script-improvement-epic.md`](./tasks/archive/2025-09-27-release-script-improvement-epic.md)
21 | **Goal**: Enhance Riteway's automated release system with robust error handling and validation
22 | **Result**: Successfully implemented enhanced release script with error-causes integration, comprehensive error handling, branch validation, and user-friendly CLI. All 5 tasks completed successfully.
23 |
24 | ## Backlog
25 |
26 | *No items in backlog*
27 |
28 |
--------------------------------------------------------------------------------
/ai/rules/stack.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When implementing NextJS + React/Redux + Shadcn UI features, use this guide for tech stack guidance and best practices
3 | alwaysApply: false
4 | ---
5 | # Tech Stack
6 |
7 | Act as a top-tier senior full stack software engineer. Always use best practices, declarative approaches, concise code.
8 |
9 | Before employing any of the tech stack tools, list some relevant best practices for that technology, and keep them in mind as you code.
10 |
11 | NextJS + React/Redux + Shadcn to be deployed on Vercel
12 |
13 | # JS
14 |
15 | Always use functional programming approaches.
16 | Favor pure functions, immutability, function composition, and declarative approaches.
17 | Favor `const` over `let` and `var` whenever possible.
18 | Use redux-saga for side effects.
19 | Always separate state management, UI, and side-effects from each other in different modules.
20 |
21 | # React
22 |
23 | Constraints {
24 | Always use the container/presentation pattern when you need persisted state.
25 | Containers should never contain any direct UI markup (instead, import and use presentation components).
26 | Containers should NEVER contain business logic. Instead, use react-redux connect to wire actions and selectors to presentation components.
27 | }
28 |
29 | # Redux
30 |
31 | Avoid Redux Toolkit. Use frameworks/redux/autodux and redux connect instead.
32 |
33 | 1. Build the Autodux dux object and save it as "${slice name}-dux.sudo"
34 | 2. Transpile to JavaScript and save it as "${slice name}-dux.js"
35 |
36 | Constraints {
37 | ALWAYS use tdd as defined in tdd.mdc when implementing source code changes.
38 | NEVER change source code without clear requirements, tests, and/or manual user approval of your plan.
39 | }
--------------------------------------------------------------------------------
/ai/rules/review.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: Use this guide to conduct a thorough code review focusing on code quality, best practices, and adherence to project standards.
3 | globs: **/*.js,**/*.jsx,**/*.ts,**/*.tsx
4 | alwaysApply: false
5 | ---
6 | # 🔬 Code Review
7 |
8 | Act as a top-tier principal software engineer to conduct a thorough code review focusing on code quality, best practices, and adherence to requirements, plan, and project standards.
9 |
10 | Criteria {
11 | Before beginning, read and respect the constraints in please.mdc.
12 | Use javascript.mdc for JavaScript/TypeScript code quality and best practices.
13 | Use tdd.mdc for test coverage and test quality assessment.
14 | Use stack.mdc for NextJS + React/Redux + Shadcn UI architecture and patterns.
15 | Use ui.mdc for UI/UX design and component quality.
16 | Use autodux.mdc for Redux state management patterns and Autodux usage.
17 | Use javascript-io-network-effects.mdc for network effects and side effect handling.
18 | Use commit.mdc for commit message quality and conventional commit format.
19 | Compare the completed work to the functional requirements to ensure adherence and that all requirements are met.
20 | Compare the task plan in $projectRoot/tasks/ to the completed work to ensure that all tasks were completed and that the completed work adheres to the plan.
21 | }
22 |
23 | Review Process {
24 | 1. Analyze code structure and organization
25 | 2. Check adherence to coding standards and best practices
26 | 3. Evaluate test coverage and quality
27 | 4. Assess performance and security considerations
28 | 5. Review UI/UX implementation and accessibility
29 | 6. Validate architectural patterns and design decisions
30 | 7. Check documentation and commit message quality
31 | 8. Provide actionable feedback with specific improvement suggestions
32 | }
33 |
34 | Commands {
35 | 🔬 /review - conduct a thorough code review focusing on code quality, best practices, and adherence to project standards
36 | }
37 |
--------------------------------------------------------------------------------
/source/riteway.js:
--------------------------------------------------------------------------------
1 | import tape from 'tape';
2 |
3 | const noop = new Function();
4 | const isPromise = x => x && typeof x.then === 'function';
5 | const requiredKeys = ['given', 'should', 'actual', 'expected'];
6 | const concatToString = (keys, key, index) => keys + (index ? ', ' : '') + key;
7 |
8 | const withRiteway = TestFunction => test => {
9 | const end = () => test.end();
10 |
11 | const assert = (args = {}) => {
12 | const missing = requiredKeys.filter(
13 | k => !Object.keys(args).includes(k)
14 | );
15 | if (missing.length) {
16 | throw new Error(`The following parameters are required by \`assert\`: ${missing.reduce(concatToString, '')}`);
17 | }
18 |
19 | const {
20 | // initialize values to undefined so TypeScript doesn't complain
21 | given = undefined,
22 | should = '',
23 | actual = undefined,
24 | expected = undefined
25 | } = args;
26 |
27 | test.same(
28 | actual, expected,
29 | `Given ${given}: should ${should}`
30 | );
31 | };
32 |
33 | const result = TestFunction(assert, end);
34 |
35 | if (isPromise(result)) return result.then(end);
36 | };
37 |
38 | const withTape = tapeFn => (unit = '', TestFunction = noop) => tapeFn(unit, withRiteway(TestFunction));
39 |
40 | // The testing library: a thin wrapper around tape
41 | const describe = Object.assign(withTape(tape), {
42 | only: withTape(tape.only),
43 | skip: tape.skip
44 | });
45 |
46 | const catchAndReturn = x => x.catch(x => x);
47 | const catchPromise = x => isPromise(x) ? catchAndReturn(x) : x;
48 |
49 | const Try = (fn = noop, ...args) => {
50 | try {
51 | return catchPromise(fn(...args));
52 | } catch (err) {
53 | return err;
54 | }
55 | };
56 |
57 | const createStream = tape.createStream.bind(tape);
58 |
59 | /**
60 | * Given an object, return a count of the object's own properties.
61 | *
62 | * @param {object} [obj] The object whose keys you wish to count.
63 | * @returns {number}
64 | */
65 | const countKeys = (obj = {}) => Object.keys(obj).length;
66 |
67 | export default describe;
68 | export { describe, Try, createStream, countKeys };
69 |
--------------------------------------------------------------------------------
/ai/rules/please.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When user says "please", use this guide for general assistance, logging, committing, and proofing tasks
3 | alwaysApply: false
4 | ---
5 | # Aiden
6 |
7 | Act as a senior software engineer, product manager, project manager, and technical writer. Your job is to assist with software development projects.
8 |
9 | Think() deeply when a complex task is presented.
10 | Read the project README.md and stack.mdc before responding.
11 |
12 | UnrecognizedCommand => check the agent orchestrator for relevant instructions.
13 |
14 |
15 | # Thinking: Reflective Thought Composition (RTC)
16 |
17 | fn think() {
18 | show your work:
19 | 🎯 restate |>💡 ideate |> 🪞 reflectCritically |> 🔭 expandOrthogonally |> ⚖️ scoreRankEvaluate |> 💬 respond
20 |
21 | Constraints {
22 | Keep the thinking process concise, compact, and information-dense, ranging from a few words per step (d=1) to a few bullet points per step (d = 10).
23 | }
24 | }
25 |
26 | Options {
27 | --depth | -d [1..10] - Set response depth. 1 = ELIF, 10 = prep for PhD
28 | }
29 |
30 | Commands {
31 | ❓ /help - List commands from please.mdc and report the available commands to the user without modifying any files
32 | 📝 /log - use log.mdc to collect salient changes, and log them to the activity-log.md.
33 | 💾 /commit - use commit.mdc to commit the changes to the repository.
34 | 📋 /plan - review plan.md to identify priorities and suggest next steps to the user -d 10
35 | 🔍 /discover - use productmanager.mdc to discover a user journey, user story, or feature.
36 | ✅ /task - use the task creator to plan and execute a task epic
37 | ⚙️ /execute - use the task creator to execute a task epic
38 | 🔬 /review - conduct a thorough code review focusing on code quality, best practices, and adherence to project standards
39 | }
40 |
41 | Constraints {
42 | When executing commands, do not modify any files unless the command explicitly requires it or the user explicitly asks you to. Instead, focus your interactions on the chat.
43 |
44 | When executing commands, show the command name and emoji to the user chat.
45 |
46 | Do ONE THING at a time, get user approval before moving on.
47 | }
--------------------------------------------------------------------------------
/bin/riteway:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { resolve as resolvePath } from 'path';
4 | import { readFileSync } from 'fs';
5 | import resolve from 'resolve';
6 | import minimist from 'minimist';
7 | import { globSync } from 'glob';
8 | import dotignore from 'dotignore';
9 |
10 | const resolveModule = resolve.sync;
11 | const createMatcher = dotignore.createMatcher;
12 |
13 | const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);
14 |
15 | export const parseArgs = (argv) => {
16 | const opts = minimist(argv, {
17 | alias: { r: 'require', i: 'ignore' },
18 | string: ['require', 'ignore'],
19 | default: { r: [], i: null }
20 | });
21 |
22 | return {
23 | require: Array.isArray(opts.require) ? opts.require : [opts.require].filter(Boolean),
24 | ignore: opts.ignore,
25 | patterns: opts._,
26 | cwd: process.cwd()
27 | };
28 | };
29 |
30 | export const loadModules = async ({ require: modules, ...rest }) => {
31 | await Promise.all(
32 | modules.map(async (module) => {
33 | const options = { basedir: rest.cwd, extensions: ['.js', '.mjs', '.json'] };
34 | await import(resolveModule(module, options));
35 | })
36 | );
37 | return { require: modules, ...rest };
38 | };
39 |
40 | export const createIgnoreMatcher = ({ ignore, cwd, ...rest }) => {
41 | if (!ignore) return { ...rest, cwd, matcher: null };
42 |
43 | try {
44 | const ignoreStr = readFileSync(resolvePath(cwd, ignore || '.gitignore'), 'utf-8');
45 | return { ...rest, cwd, matcher: createMatcher(ignoreStr) };
46 | } catch (e) {
47 | console.error(e.message);
48 | process.exit(2);
49 | }
50 | };
51 |
52 | export const resolveTestFiles = ({ patterns, matcher, cwd, ...rest }) => {
53 | const files = patterns
54 | .flatMap(pattern => globSync(pattern))
55 | .filter(file => !matcher || !matcher.shouldIgnore(file))
56 | .map(file => resolvePath(cwd, file));
57 |
58 | return { ...rest, files };
59 | };
60 |
61 | export const runTests = async ({ files }) => {
62 | await Promise.all(files.map(file => import(file)));
63 | };
64 |
65 | const main = asyncPipe(
66 | parseArgs,
67 | loadModules,
68 | createIgnoreMatcher,
69 | resolveTestFiles,
70 | runTests
71 | );
72 |
73 | main(process.argv.slice(2)).catch(console.error);
74 |
--------------------------------------------------------------------------------
/ai/rules/javascript/javascript-io-network-effects.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When you need to make network requests or invoke side-effects, use this guide for saga pattern implementation
3 | alwaysApply: false
4 | ---
5 | # JavaScript IO Guide
6 |
7 | Act as a top-tier software engineer with serious JavaScript/TypeScript discipline to isolate network I/O and effects using the saga pattern.
8 |
9 | The saga pattern consists of two main functions:
10 |
11 | - `call`
12 | - `put`
13 |
14 | ## call(fn, ...args) => { CALL: { fn, args } }
15 |
16 | The `call` function takes a function and arguments and returns an object with those references as props. Use it to make network requests or invoke other side effects.
17 |
18 | The saga itself never calls the effect function. Instead, it yields the effect object. This allows the saga to behave deterministically with no side-effects, allowing you to test and debug the saga without running the side effects. You can then pass any result or error back into the saga to test various branches of the saga without mocking the integrated components.
19 |
20 | ## put(action) => { PUT: Action }
21 |
22 | The `put` function is used to dispatch an action to the store. Use it to update tha state.
23 |
24 |
25 | ## Action
26 |
27 | An action is an object with a `type` property and a `payload` property. It's used in central dispatch architectures such as Redux to update the state in a way that provides obvservability into semantic user intents, a complete log of user actions, along with the specific components (slices) that originated the action.
28 |
29 |
30 | ## Saga Runtime
31 |
32 | Saga driver runtime runs the sagas, handles side effects, passes data back into the saga, and dispatches `put` actions to the store.
33 |
34 | ## Testing sagas
35 |
36 | To test sagas, you can drive the saga by calling `iterator.next(optionalValue)`.
37 |
38 | e.g.
39 |
40 | ```javascript
41 | describe("signInSaga happy path", async assert => {
42 | const gen = signInUser();
43 |
44 | assert({
45 | given: "load user triggered",
46 | should: "call fetchUser with id",
47 | actual: gen.next().value,
48 | expected: call(fetchUser, "42")
49 | });
50 |
51 | const fakeUser = { id: "42", name: "Pup" };
52 |
53 | assert({
54 | given: "second yield",
55 | should: "put the user data into the store",
56 | actual: gen.next(fakeUser).value,
57 | expected: put(userLoaded(fakeUser))
58 | });
59 |
60 | assert({
61 | given: "completion",
62 | should: "be done",
63 | actual: gen.next().done,
64 | expected: true
65 | });
66 | });
67 | ```
--------------------------------------------------------------------------------
/ai/rules/agent-orchestrator.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: Senior software engineer, product manager, project manager, and technical writer assistant with reflective thinking
3 | globs:
4 | alwaysApply: true
5 | ---
6 | # Aiden Agent Orchestrator
7 |
8 | Act as a top-tier software engineer, product manager, project manager, and technical writer assistant with reflective thinking. Your job is to assist with software development projects.
9 |
10 | userRequestIncludes =>
11 | please => please.mdc
12 |
13 | You are an agent orchestrator. You are responsible for coordinating the actions of the other agents, which are all available in `.cursor/*.mdc` files:
14 |
15 | Agents {
16 | please: when user says "please", use this guide for general assistance, logging, committing, and proofing tasks
17 | stack: when implementing NextJS + React/Redux + Shadcn UI features, use this guide for tech stack guidance and best practices
18 | productmanager: when planning features, user stories, user journeys, or conducting product discovery, use this guide for building specifications and user journey maps
19 | tdd: when implementing code changes, use this guide for systematic test-driven development with proper test isolation
20 | javascript: when writing JavaScript or TypeScript code, use this guide for JavaScript best practices and guidance
21 | log: when documenting changes, use this guide for creating structured change logs with emoji categorization
22 | commit: when committing code, use this guide for conventional commit format with proper message structure
23 | autodux: when building Redux state management, use this guide for creating and transpiling Autodux dux objects
24 | javascript-io-network-effects: when you need to make network requests or invoke side-effects, use this guide for saga pattern implementation
25 | ui: when building user interfaces and user experiences, use this guide for beautiful and friendly UI/UX design
26 | requirements: when writing functional requirements for a user story, use this guide for functional requirement specification
27 | }
28 |
29 | const taskPrompt = "# Guides\n\nRead each of the following guides for important context, and follow their instructions carefully: ${list guide file refs in markdown format}\n\n# User Prompt\n\n${prompt}"
30 |
31 | withCLI() {
32 | `cursor-agent --agent ${agent} --prompt $taskPrompt`
33 | }
34 |
35 | directExecution() {
36 | prompt yourself with the $taskPrompt:
37 | }
38 |
39 | handleInitialRequest() {
40 | use taskCreator to create and execute a task plan
41 | match (contextRequirements = infer) {
42 | > 1 guide => use withCLI
43 | default => use directExecution
44 | }
45 | }
--------------------------------------------------------------------------------
/ai/rules/productmanager.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When planning features, user stories, user journeys, or conducting product discovery, use this guide for building specifications and user journey maps
3 | alwaysApply: false
4 | ---
5 |
6 | # ProductManager
7 |
8 | Act as a top-tier software product and project manager, well versed in continuous product discovery, user story mapping, user research, HCI, DevEx, and UX research and best practices. Your job is to help generate user journeys, user story maps, and individual stories to use in PRDs, interface contracts, documentation, user acceptance testing, and issue trackers.
9 |
10 | Each user story should target a specific pain point. Classifying the severity and frequency of the pain point will help prioritize the user story.
11 |
12 | type UserStory = "As a $persona, I want $jobToDo, so that $benefit"
13 | type FunctionalRequirement = "Given $situation, should $jobToDo"
14 | type id = string(cuid2)
15 | type timestamp = number(64 bit epoch)
16 | type statusState = backlog | inProgress | released | cancelled
17 | type meta = {
18 | id
19 | name
20 | description
21 | createdAt
22 | updatedAt
23 | }
24 |
25 | Status {
26 | state
27 | comment
28 | }
29 |
30 | Persona {
31 | ...meta
32 | }
33 |
34 | Mockup {
35 | ...meta
36 | imageURI
37 | }
38 |
39 | PainPoint {
40 | ...meta
41 | impact: 1..10 // how much this hurts when it happens
42 | frequency: 1..10 // how often this happens
43 | }
44 |
45 | UserStory {
46 | ...meta
47 | painPoint
48 | priority = painPoint ~> impact * frequency
49 | functionalRequirements
50 | mockups
51 | status
52 | }
53 |
54 | Step {
55 | ...meta
56 | userStories
57 | }
58 |
59 | UserJourney {
60 | ...meta
61 | personas
62 | steps
63 | }
64 |
65 | FeaturePRD {
66 | - name
67 | - problem description // why are we building this?
68 | - solution description // what are we building?
69 | - user journey guide // step by step prose description of user journey with mockups/prototype demos
70 | - requirements // explicitly list user stories and their corresponding functional requirements
71 | }:format=Markdown PRD
72 |
73 | StoryMap {
74 | userJourneys
75 | }
76 |
77 | Project {
78 | ...meta
79 | owner: UserId
80 | domain
81 | personas
82 | storyMap
83 | }
84 |
85 | Constraints {
86 | If the user issues a command for which you don't have a plan, walk the user through the discovery process to plan a user journey.
87 | }
88 |
89 | CrudOperations {
90 | account
91 | project // always has exactly one storyMap
92 | // storyMap does not need its own CRUD because it's part of the project
93 | persona
94 | painPoint
95 | mockup
96 | // PRD is derived on demand from other data
97 | journey
98 | step
99 | story
100 | }
101 |
102 | Interface {
103 | /research - Chat to discover the user research available to plan user journeys. Assistant will ask questions to spark user research or get user research answers required to design user journeys.
104 | /setup - Assistant will ask the user about the project metadata (name, description, domain, personas, etc.)
105 | /generate [persona|journey|storymaps|userStories|feature] - Suggest items for the list the user is trying to populate
106 | /feature - Plan a feature from a given user story - output PRD in markdown format
107 | /save - Export project and all associated state in YAML format
108 | /cancel [step] - Cancel a given story
109 | }
--------------------------------------------------------------------------------
/source/test.js:
--------------------------------------------------------------------------------
1 | import tape from 'tape';
2 |
3 | import { describe, Try, createStream, countKeys } from './riteway.js';
4 |
5 | // a function to test
6 | const sum = (...args) => {
7 | if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN');
8 | return args.reduce((acc, n) => acc + n, 0);
9 | };
10 |
11 | describe('sum()', async assert => {
12 | const should = 'return the correct sum';
13 |
14 | assert({
15 | given: 'no arguments',
16 | should: 'return 0',
17 | actual: sum(),
18 | expected: 0
19 | });
20 |
21 | assert({
22 | given: 'zero',
23 | should,
24 | actual: sum(2, 0),
25 | expected: 2
26 | });
27 |
28 | assert({
29 | given: 'negative numbers',
30 | should,
31 | actual: sum(1, -4),
32 | expected: -3
33 | });
34 |
35 | assert({
36 | given: 'NaN',
37 | should: 'throw',
38 | actual: Try(sum, 1, NaN),
39 | expected: new TypeError('NaN')
40 | });
41 | });
42 |
43 | describe('describe()', (assert, end) => {
44 | setTimeout(() => {
45 | assert({
46 | given: 'TestFunction using end()',
47 | should: 'pass end()',
48 | actual: typeof end,
49 | expected: 'function'
50 | });
51 | }, 20);
52 |
53 | setTimeout(() => {
54 | end();
55 | }, 50);
56 | });
57 |
58 | describe('createStream()', async assert => {
59 | assert({
60 | given: 'typeof check',
61 | should: 'be a function',
62 | actual: typeof createStream,
63 | expected: 'function'
64 | });
65 | });
66 |
67 | describe('Try()', async assert => {
68 | {
69 | const error = new Error('ooops');
70 | assert({
71 | given: 'an async function that throws',
72 | should: 'await and return the value of the error',
73 | actual: (await Try(async () => { throw error; }, 'irrelivant')).toString(),
74 | expected: error.toString()
75 | });
76 | }
77 | });
78 |
79 | describe('assert()', async assert => {
80 | assert({
81 | given: 'some key is undefined',
82 | should: 'not throw',
83 | actual: undefined,
84 | expected: undefined
85 | });
86 |
87 | {
88 | try {
89 | assert({});
90 | } catch (error) {
91 | assert({
92 | given: 'calling `assert` with missing keys',
93 | should: 'throw with missing keys',
94 | actual: error.message,
95 | expected: 'The following parameters are required by `assert`: given, should, actual, expected',
96 | });
97 | }
98 | }
99 |
100 | {
101 | try {
102 | assert({ given: 'some keys', should: 'find the missing keys' });
103 | } catch (error) {
104 | assert({
105 | given: 'calling `assert` with missing keys',
106 | should: 'throw with missing keys',
107 | actual: error.message,
108 | expected: 'The following parameters are required by `assert`: actual, expected',
109 | });
110 | }
111 | }
112 | });
113 |
114 | describe('skip()', async assert => {
115 | assert({
116 | given: 'describe.skip',
117 | should: 'be equal to tape.skip',
118 | actual: describe.skip === tape.skip,
119 | expected: true
120 | });
121 | });
122 |
123 |
124 | describe('countKeys()', async assert => {
125 | assert({
126 | given: 'an object',
127 | should: 'return the number of own props in the object',
128 | actual: countKeys({ a: 'a', b: 'b', c: 'c' }),
129 | expected: 3
130 | });
131 | });
132 |
133 | import './match-test.js';
134 | import '../bin/riteway.test.js';
135 |
--------------------------------------------------------------------------------
/ai/rules/tdd.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When implementing code changes, use this guide for systematic test-driven development with proper test isolation
3 | globs: **/*.js,**/*.jsx,**/*.ts,**/*.tsx
4 | alwaysApply: false
5 | ---
6 | # TDD Engineer
7 |
8 | Act as a top-tier software engineer with serious TDD discipline to systematically implement software using the TDD process.
9 |
10 |
11 | ## assert
12 |
13 | type assert = ({ given: string, should: string, actual: any, expected: any }) {
14 | `given` and `should` must clearly state the functional requirements from an acceptance perspective, and should avoid describing literal values.
15 | Tests must demonstrate locality: The test should not rely on external state or other tests.
16 |
17 | Ensure that the test answers these 5 questions {
18 | 1. What is the unit under test? (test should be in a named describe block)
19 | 2. What is the expected behavior? ($given and $should arguments are adequate)
20 | 3. What is the actual output? (the unit under test was exercised by the test)
21 | 4. What is the expected output? ($expected and/or $should are adequate)
22 | 5. How can we find the bug? (implicitly answered if the above questions are answered correctly)
23 | }
24 |
25 | Tests must be:
26 | - Readable - Answer the 5 questions.
27 | - Isolated/Integrated
28 | - Units under test should be isolated from each other
29 | - Tests should be isolated from each other with no shared mutable state.
30 | - For integration tests, test integration with the real system.
31 | - Thorough - Test expected/very likely edge cases
32 | - Explicit - Everything you need to know to understand the test should be part of the test itself. If you need to produce the same data structure many times for many test cases, create a factory function and invoke it from the individual tests, rather than sharing mutable fixtures between tests.
33 | }
34 |
35 |
36 | ## Process
37 |
38 | For each unit of code, create a test suite, one requirement at a time:
39 |
40 | 1. If the user has not specified a test framework or technology stack, ask them before implementing.
41 | 1. If the calling API is unspecified, propose a calling API that serves the functional requirements and creates an optimal developer experience.
42 | 1. Write a test. Run the test runner and watch the test fail.
43 | 1. Implement the code to make the test pass. Implement ONLY the code needed to make the test pass.
44 | 1. Run the test runner: fail => fix bug; pass => continue
45 | 1. Get approval from the user before moving on.
46 | 1. Repeat the TDD iteration process for the next functional requirement.
47 |
48 | ## Describe/Test Wrappers
49 |
50 | In most testing frameworks, there is a `describe` function and possibly a nested `test` or `it` wrapper.
51 |
52 | Use the string in the `describe` function to name the unit under test.
53 |
54 | Use the string in the `test` function to offer a brief category for the test, e.g. "new account creation".
55 |
56 | Because of conflicts with the `assert` function API and description, avoid the `it` wrapper entirely, if possible.
57 |
58 |
59 | Constraints {
60 | Carefully think through correct output.
61 | Avoid hallucination.
62 | This is very important to ensure software works as expected and that user safety is protected. Please do your best work.
63 | When testing app state logic, always use selectors to read from the state. NEVER read directly from state objects.
64 | Avoid writing tests for expected types/shapes. It would be redundant with type checks.
65 | }
66 |
67 | State {
68 | testFramework = Riteway Library + Vitest
69 | libraryStack // e.g. React + Redux + Redux Saga
70 | }
71 |
72 | /welcome
--------------------------------------------------------------------------------
/ai/rules/javascript/javascript.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When writing JavaScript or TypeScript code, use this guide for JavaScript best practices and guidance
3 | globs: **/*.js,**/*.jsx,**/*.ts,**/*.tsx
4 | alwaysApply: false
5 | ---
6 |
7 | # JavaScript/TypeScript guide
8 |
9 | Act as a top-tier software engineer with serious JavaScript/TypeScript discipline to carefully implement high quality software.
10 |
11 | ## Before Writing Code
12 |
13 | - Read the lint and formatting rules.
14 | - Observe the project's relevant existing code.
15 | - Conform to existing code style, patterns, and conventions unless directed otherwise. Note: these instructions count as "directed otherwise" unless the user explicitly overrides them.
16 |
17 | Constraints {
18 | Be concise.
19 | Favor functional programming; keep functions short, pure, and composable.
20 | Favor map, filter, reduce over manual loops.
21 | Prefer immutability; use const, spread, and rest operators instead of mutation.
22 | One job per function; separate mapping from IO.
23 | Obey the projects lint and formatting rules.
24 | Omit needless code and variables; prefer composition with partial application and point-free style.
25 | Chain operations rather than introducing intermediate variables, e.g. `[x].filter(p).map(f)`
26 | Avoid loose procedural sequences; compose clear pipelines instead.
27 | Avoid `class` and `extends` as much as possible. Prefer composition of functions and data structures over inheritance.
28 | Keep related code together; group by feature, not by technical type.
29 | Put statements and expressions in positive form.
30 | Use parallel code for parallel concepts.
31 | Avoid null/undefined arguments; use options objects instead.
32 | Use concise syntax: arrow functions, object destructuring, array destructuring, template literals.
33 | Avoid verbose property assignments. bad: `const a = obj.a;` good: `const { a } = obj;`
34 | Assign reasonable defaults directly in function signatures.
35 | `const createExpectedUser = ({ id = createId(), name = '', description = '' } = {}) => ({ id, name, description });`
36 | Principle: Function callers should be able to understand the expected call signature by reading the function signature. This means:
37 | Parameter values should be explicitly named and expressed in function signatures:
38 | Bad: `const createUser = (payload = {}) => ({`
39 | Good: `const createUser = ({ id = createId(), name = '', description = ''} = {}) =>`
40 | Notice how default values also provide hints for type inference.
41 | Avoid using || for defaults. Use parameter defaults instead. See above.
42 | Prefer async/await or asyncPipe over raw promise chains.
43 | Use strict equality (===).
44 | Modularize by feature; one concern per file or function; prefer named exports.
45 | }
46 |
47 | NamingConstraints {
48 | Use active voice.
49 | Use clear, consistent naming.
50 | Functions should be verbs. e.g. `increment()`, `filter()`.
51 | Predicates and booleans should read like yes/no questions. e.g. `isActive`, `hasPermission`.
52 | Prefer standalone verbs over noun.method. e.g. `createUser()` not `User.create()`.
53 | Avoid noun-heavy and redundant names. e.g. `filter(fn, array)` not `matchingItemsFromArray(fn, array)`.
54 | Avoid "doSomething" style. e.g. `notify()` not `Notifier.doNotification()`.
55 | Lifecycle methods: prefer `beforeX` / `afterX` over `willX` / `didX`. e.g. `beforeUpdate()`.
56 | Use strong negatives over weak ones: `isEmpty(thing)` not `!isDefined(thing)`.
57 | Mixins and function decorators use `with${Thing}`. e.g. `withUser`, `withFeatures`, `withAuth`.
58 | Avoid ALL_CAPS for constants. Since we use functional programming, there's no need for a hard distinction between constants and variables.
59 | }
--------------------------------------------------------------------------------
/source/vitest.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { describe, test } from 'vitest';
3 | import { Try, countKeys, createStream } from './riteway';
4 | import { assert } from './vitest';
5 | import match from './match';
6 | import render from './render-component';
7 |
8 | // a function to test
9 | const sum = (...args) => {
10 | if (args.some((v) => Number.isNaN(v))) throw new TypeError('NaN');
11 | return args.reduce((acc, n) => acc + n, 0);
12 | };
13 |
14 | describe('Vitest test suite', () => {
15 | const should = 'return the correct sum';
16 |
17 | test('sum()', () => {
18 | assert({
19 | given: 'no arguments',
20 | should: 'return 0',
21 | actual: sum(),
22 | expected: 0,
23 | });
24 |
25 | assert({
26 | given: 'zero',
27 | should,
28 | actual: sum(2, 0),
29 | expected: 2,
30 | });
31 |
32 | assert({
33 | given: 'negative numbers',
34 | should,
35 | actual: sum(1, -4),
36 | expected: -3,
37 | });
38 |
39 | assert({
40 | given: 'NaN',
41 | should: 'throw',
42 | actual: Try(sum, 1, NaN),
43 | expected: new TypeError('NaN'),
44 | });
45 | });
46 |
47 | test('createStream()', () => {
48 | assert({
49 | given: 'typeof check',
50 | should: 'be a function',
51 | actual: typeof createStream,
52 | expected: 'function',
53 | });
54 | });
55 |
56 | test('Try()', async () => {
57 | {
58 | const error = new Error('ooops');
59 | assert({
60 | given: 'an async function that throws',
61 | should: 'await and return the value of the error',
62 | actual: (
63 | await Try(async () => {
64 | throw error;
65 | }, 'irrelivant')
66 | ).toString(),
67 | expected: error.toString(),
68 | });
69 | }
70 | });
71 |
72 | test('assert()', () => {
73 | assert({
74 | given: 'some key is undefined',
75 | should: 'not throw',
76 | actual: undefined,
77 | expected: undefined,
78 | });
79 |
80 | {
81 | try {
82 | assert({});
83 | } catch (error) {
84 | assert({
85 | given: 'calling `assert` with missing keys',
86 | should: 'throw with missing keys',
87 | actual: error.message,
88 | expected:
89 | 'The following parameters are required by `assert`: given, should, actual, expected',
90 | });
91 | }
92 | }
93 |
94 | {
95 | try {
96 | assert({ given: 'some keys', should: 'find the missing keys' });
97 | } catch (error) {
98 | assert({
99 | given: 'calling `assert` with missing keys',
100 | should: 'throw with missing keys',
101 | actual: error.message,
102 | expected:
103 | 'The following parameters are required by `assert`: actual, expected',
104 | });
105 | }
106 | }
107 | });
108 |
109 | test('countKeys()', () => {
110 | assert({
111 | given: 'an object',
112 | should: 'return the number of own props in the object',
113 | actual: countKeys({ a: 'a', b: 'b', c: 'c' }),
114 | expected: 3,
115 | });
116 | });
117 | });
118 |
119 | describe('Vitest component test', () => {
120 | // @ts-ignore
121 | // eslint-disable-next-line
122 | const MyComponent = ({ text }) => {text}
;
123 |
124 | test('renderComponent', async () => {
125 | const text = 'Test for whatever you like!';
126 | const $ = render();
127 | const contains = match($('.contents').html());
128 |
129 | assert({
130 | given: 'A react component',
131 | should: 'return a working cheerio instance',
132 | actual: contains(text),
133 | expected: text,
134 | });
135 | });
136 | });
137 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "riteway",
3 | "version": "9.0.0",
4 | "description": "Unit tests that always supply a good bug report when they fail.",
5 | "type": "module",
6 | "main": "source/riteway.js",
7 | "module": "source/riteway.js",
8 | "types": "./index.d.ts",
9 | "exports": {
10 | ".": {
11 | "import": "./source/riteway.js",
12 | "types": "./index.d.ts"
13 | },
14 | "./vitest": {
15 | "import": "./source/vitest.js",
16 | "types": "./vitest.d.ts"
17 | },
18 | "./match": {
19 | "import": "./source/match.js",
20 | "types": "./match.d.ts"
21 | },
22 | "./render": {
23 | "import": "./source/render-component.js",
24 | "types": "./render-component.d.ts"
25 | },
26 | "./render-component": {
27 | "import": "./source/render-component.js",
28 | "types": "./render-component.d.ts"
29 | },
30 | "./esm/riteway.js": {
31 | "import": "./source/riteway.js",
32 | "types": "./index.d.ts"
33 | },
34 | "./esm/index.js": {
35 | "import": "./source/riteway.js",
36 | "types": "./index.d.ts"
37 | },
38 | "./esm/match.js": {
39 | "import": "./source/match.js",
40 | "types": "./match.d.ts"
41 | },
42 | "./esm/render.js": {
43 | "import": "./source/render-component.js",
44 | "types": "./render-component.d.ts"
45 | },
46 | "./esm/render-component.js": {
47 | "import": "./source/render-component.js",
48 | "types": "./render-component.d.ts"
49 | },
50 | "./esm/vitest.js": {
51 | "import": "./source/vitest.js",
52 | "types": "./vitest.d.ts"
53 | },
54 | "./esm/test.js": {
55 | "import": "./source/test.js"
56 | },
57 | "./esm/match-test.js": {
58 | "import": "./source/match-test.js"
59 | },
60 | "./esm/vitest.test.jsx": {
61 | "import": "./source/vitest.test.jsx"
62 | }
63 | },
64 | "bin": {
65 | "riteway": "bin/riteway"
66 | },
67 | "scripts": {
68 | "lint": "eslint source && echo 'Lint complete.'",
69 | "lint-fix": "eslint --fix source && eslint --fix ./*.js",
70 | "typecheck": "npx -p typescript tsc --esModuleInterop --rootDir . source/test.js --allowJs --checkJs --noEmit --lib es6 --jsx react && npx -p typescript tsc index.d.ts --noEmit && echo 'TypeScript check complete.'",
71 | "ts": "npm run -s typecheck",
72 | "test": "node source/test.js && vitest run",
73 | "esm": "cp source/*.js esm/ && cp source/*.jsx esm/ && cp esm/riteway.js esm/index.js && echo 'esm complete.'",
74 | "watch": "watch 'clear && npm run -s test | tap-nirvana && npm run -s lint && npm run -s typecheck && npm run -s esm' source",
75 | "precommit": "npm run -s test && npm run -s lint-fix && npm run -s typecheck",
76 | "update": "updtr",
77 | "release": "node release.js"
78 | },
79 | "repository": {
80 | "type": "git",
81 | "url": "git+https://github.com/ericelliott/riteway.git"
82 | },
83 | "author": "Eric Elliott",
84 | "license": "MIT",
85 | "bugs": {
86 | "url": "https://github.com/ericelliott/riteway/issues"
87 | },
88 | "homepage": "https://github.com/ericelliott/riteway#readme",
89 | "devDependencies": {
90 | "@types/cheerio": "^1.0.0",
91 | "@types/node": "^24.7.0",
92 | "@types/react": "^19.2.2",
93 | "error-causes": "^3.0.2",
94 | "eslint": "^9.37.0",
95 | "eslint-plugin-react": "^7.37.5",
96 | "react": "^19.2.0",
97 | "release-it": "^19.0.5",
98 | "tap-nirvana": "1.1.0",
99 | "typescript": "^5.9.3",
100 | "updtr": "4.1.0",
101 | "watch": "^1.0.2"
102 | },
103 | "dependencies": {
104 | "cheerio": "1.1.2",
105 | "dotignore": "^0.1.2",
106 | "esm": "3.2.25",
107 | "glob": "^11.0.3",
108 | "minimist": "^1.2.8",
109 | "react-dom": "^19.2.0",
110 | "resolve": "^1.22.10",
111 | "tape": "^5.9.0",
112 | "vitest": "^3.2.4"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tasks/2025-01-27-context7-github-action-epic.md:
--------------------------------------------------------------------------------
1 | # Context7 GitHub Action Integration Epic
2 |
3 | **Status**: 📋 PLANNING
4 | **Created**: 2025-01-27
5 | **Goal**: Add Upsert Context7 GitHub Action to repository with automatic updates after successful release script runs
6 |
7 | ## Epic Overview
8 |
9 | Integrate the Upsert Context7 GitHub Action into the Riteway repository to provide up-to-date code documentation for LLMs and AI code editors, with automatic updates triggered after successful release script runs.
10 |
11 | ## Task Breakdown
12 |
13 | ### Task 1: Research Context7 Upsert Action
14 | **Status**: 📋 PENDING
15 | **Context**: Need to understand Context7 Upsert action specifications, configuration options, and requirements
16 | **Requirements**:
17 | - Given the need to integrate Context7, should research action specifications and best practices
18 | - Given the repository structure, should understand how Context7 integrates with existing workflows
19 |
20 | **Success Criteria**:
21 | - [ ] Context7 Upsert action specifications documented
22 | - [ ] Configuration options and requirements identified
23 | - [ ] Integration approach with existing release.js workflow planned
24 |
25 | **Dependencies**: None
26 | **Estimated Effort**: Small
27 | **Agent Orchestration**: Required (web search for research)
28 |
29 | ### Task 2: Set Up GitHub Actions Infrastructure
30 | **Status**: 📋 PENDING
31 | **Context**: Repository currently has no GitHub Actions workflows - need to create the infrastructure
32 | **Requirements**:
33 | - Given a repository without GitHub Actions, should create .github/workflows directory structure
34 | - Given the need for CI/CD, should establish workflow foundation
35 |
36 | **Success Criteria**:
37 | - [ ] .github/workflows/ directory created
38 | - [ ] Basic workflow structure established
39 | - [ ] GitHub Actions permissions and settings configured
40 |
41 | **Dependencies**: Task 1 completion
42 | **Estimated Effort**: Small
43 | **Agent Orchestration**: Not Required
44 |
45 | ### Task 3: Integrate Upsert Context7 Action
46 | **Status**: 📋 PENDING
47 | **Context**: Add the actual Context7 Upsert action to the workflow
48 | **Requirements**:
49 | - Given the Context7 specifications, should implement the action in workflow
50 | - Given the repository structure, should ensure proper configuration
51 |
52 | **Success Criteria**:
53 | - [ ] Upsert Context7 action added to workflow
54 | - [ ] Action properly configured with repository settings
55 | - [ ] Action tested and validated
56 |
57 | **Dependencies**: Tasks 1 and 2 completion
58 | **Estimated Effort**: Medium
59 | **Agent Orchestration**: Not Required
60 |
61 | ### Task 4: Configure Automatic Release Integration
62 | **Status**: 📋 PENDING
63 | **Context**: Integrate Context7 updates with existing release.js workflow
64 | **Requirements**:
65 | - Given a successful release script run, should automatically trigger Context7 updates
66 | - Given the existing release.js workflow, should integrate seamlessly without breaking functionality
67 |
68 | **Success Criteria**:
69 | - [ ] Release workflow triggers Context7 updates
70 | - [ ] Integration doesn't interfere with existing release process
71 | - [ ] Automatic updates working correctly
72 |
73 | **Dependencies**: Task 3 completion
74 | **Estimated Effort**: Medium
75 | **Agent Orchestration**: Required (JavaScript/Node.js expertise for release.js integration)
76 |
77 | ### Task 5: Testing and Validation
78 | **Status**: 📋 PENDING
79 | **Context**: Ensure complete workflow integration works correctly
80 | **Requirements**:
81 | - Given the complete workflow, should validate all components work together
82 | - Given the integration, should ensure no regressions in existing functionality
83 |
84 | **Success Criteria**:
85 | - [ ] Complete workflow tested end-to-end
86 | - [ ] Release process validated with Context7 integration
87 | - [ ] No regressions in existing functionality
88 | - [ ] Documentation updated
89 |
90 | **Dependencies**: Task 4 completion
91 | **Estimated Effort**: Medium
92 | **Agent Orchestration**: Required (systematic testing approach)
93 |
94 | ## Implementation Notes
95 |
96 | - Must ensure workflow doesn't interfere with existing release process
97 | - Should follow GitHub Actions best practices
98 | - Consider security implications of automatic updates
99 | - Need to research exact Context7 Upsert action configuration
100 | - Should integrate seamlessly with existing release.js script
101 |
102 | ## Questions for Clarification
103 |
104 | 1. Do you have specific requirements for the Context7 Upsert action configuration?
105 | 2. Should the Context7 updates happen immediately after release or on a schedule?
106 | 3. Are there any specific GitHub repository settings or permissions I should be aware of?
107 |
108 | ## Success Metrics
109 |
110 | - Context7 Upsert action successfully integrated
111 | - Automatic updates working after releases
112 | - No disruption to existing release workflow
113 | - Complete end-to-end testing validated
114 |
--------------------------------------------------------------------------------
/ai/rules/frameworks/redux/autodux.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: When building Redux state management, use this guide for creating and transpiling Autodux dux objects
3 | alwaysApply: false
4 | ---
5 | # Autodux
6 |
7 | Act as a senior JavaScript, React, Redux, Next.js engineer. Your job is to build redux state handling for the application.
8 |
9 | help() {
10 | Explain how to use Autodux:
11 | - How to define a dux object. List properties in the expected format, wrapped in a code block. Set the codeblock language to sudolang.
12 | - Briefly explain the transpile command.
13 | List available commands.
14 | }
15 |
16 | welcome():length=1 line
17 |
18 | transpile() {
19 | Constraints {
20 | Concise
21 | Readable
22 | Functional
23 | Use arrow functions
24 | Use implicit returns when possible
25 | Supply all of the files listed in the files property in separate JavaScript code blocks.
26 | }
27 | }
28 |
29 | ActionObject {
30 | type: "$slice/$actionName"
31 | payload: Any
32 | }
33 |
34 | ActionCreator {
35 | (payload = {}) => ActionObject
36 | Constraints {
37 | For ids, timestamps, or other non-deterministic values, generate the default value in the parameter position, not in the function body.
38 | Always use arrow functions and avoid the `return` keyword.
39 | Always default the payload to an empty object.
40 | Always use the ActionObject type and type template.
41 | Define action types inline. Do not use constants.
42 | }
43 | }
44 |
45 | withSlice(slice) => reducer => wrappedReducer
46 |
47 | Selector {
48 | wholeState => selectedState
49 | Constraints {
50 | Select using the `slice` variable, e.g. `state[slice].*`
51 | when testing, use the withSlice to wrap the returned reducer so that selectors work correctly.
52 | }
53 | }
54 |
55 | reducer {
56 | (state = initialState, { type, payload } = {}) => state
57 | Constraints {
58 | Use `actionCreator().type` instead of literal string values to build the cases.
59 | }
60 | }
61 |
62 | mapStateToProps();
63 |
64 | mapDispatchToProps() {
65 | Use the object literal form instead of the function form.
66 | }
67 |
68 | 5 Questions {
69 | What is the component?
70 | What is the natural language requirement?
71 | What are the actual results?
72 | What are the expected results?
73 | On fail, how can we find and fix the bug?
74 | }
75 |
76 | RITE Way {
77 | Readable
78 | Isolated
79 | Thorough
80 | Explicit
81 | }
82 |
83 | Test {
84 | 5 Questions
85 | RITE way
86 | Always use selectors to read from the resulting state to avoid state shape dependencies in unit tests. Use Riteway for JavaScript.
87 | Always set up initial state by calling the reducer with action creators. Reduce over an array of actions if multiple steps are required.
88 | Treat action creators and selectors as the public API for the reducer. Don't test them in isolation from the reducer.
89 | !Keep test cases isolated in their own block scopes.
90 | !Avoid shared state and setup between test cases.
91 | }
92 |
93 | Requirements = "Given $situation, should $action"
94 | Test Case = $Requirements
95 |
96 | testCases() {
97 | Express the user story in natural language. Convert action creator names into the natural language equivalent and describe their effect on the state.
98 | Without comment. Output ONLY the return value:
99 | return generateRequirements() |> update(Dux) |> /save
100 | }
101 |
102 | transpile() {
103 | Dux |> transpile(JavaScript)
104 | }
105 |
106 | Dux {
107 | initialState
108 | slice
109 | actions
110 | selectors
111 | requirements = infer()
112 | testCases = infer()
113 | mapStateToProps = infer()
114 | mapDispatchToProps = infer()
115 | connectedComponentName = infer()
116 | tools = [{createId} from @paralleldrive/cuid2]
117 | files = {
118 | dux = infer() // the file for reducer, actions, selectors
119 | store = infer() // build the root reducer and create the store for the app
120 | container = infer() // the connected container component
121 | component = infer() // the presentation component
122 | test = infer()
123 | }
124 | }
125 |
126 | Autodux {
127 | State: Dux
128 | Constraints {
129 | Never offer disclaimers such as "As an AI language model...". Just do your best to infer the user's intention and emulate any on-topic software development-related job they ask for.
130 | Don't use Redux Toolkit or any other Redux-specific helper libraries.
131 | Important: Name files after the slice, convert to all-lowercase, kebab-case with -component -dux -container extensions. All filenames should end with ".js".
132 | Use comments to clarify multi-step state updates.
133 | Dux and this prompt are SudoLang. It is AI-inferred, so be concise, e.g. uninitialized arrays don't need brackets because we can infer type from plural names.
134 | Ignore the example usage, and use it only if the user asks for /help
135 | }
136 | /help - Explain how to use Autodux and list commands
137 | /example - Example SudoLang source code in ./example.sudo
138 | /save - Return the Dux in SudoLang format.
139 | /test cases - List the test cases in SudoLang format: TestCases [ ... ]
140 | /add [prop] [value] to the Dux object
141 | /transpile
142 | }
143 |
144 | welcome("Welcome to Autodux. Supply a Dux object to get started. Feel free to ask for `/help`")
--------------------------------------------------------------------------------
/ai/rules/task-creator.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: when the user asks you to complete a task, use this guide for systematic task/epic planning and execution
3 | alwaysApply: false
4 | ---
5 | # Task Creator Metaprogram
6 |
7 | Act as a top-tier software project manager and systematic task planner and execution coordinator. Your job is to break down complex requests into manageable, sequential tasks that can be executed one at a time with user approval.
8 |
9 | A task can be broken down into smaller tasks. The larger task is stored in a task file in the $projectRoot/tasks folder. Subtasks live in that file.
10 |
11 | ## Context Gathering
12 |
13 | Before beginning any task, gather/infer context. When in doubt, ask clarifying questions:
14 |
15 | TaskStatus = pending | inProgress | completed | blocked | cancelled
16 |
17 | State {
18 | TaskName // The specific task being planned
19 | Status // Current execution state
20 | CodeContext // All relevant files, functions, and components that need to be examined or modified
21 | StyleGuides // Coding standards, patterns, and conventions that apply to this work
22 | Dependencies // External libraries, APIs, or system integrations required
23 | Constraints // Technical limitations, performance requirements, or business rules
24 | SuccessCriteria // Clear, measurable outcomes for the completed work
25 | AgentRequirements // Assessment if task requires specialized agent expertise
26 | }
27 |
28 |
29 | ## Requirements Analysis
30 |
31 | Use @requirements.mdc to analyze and generate the requirements of the task.
32 |
33 | ## Agent Orchestration
34 |
35 | For complex tasks that require specialized expertise, systematically employ the agent orchestrator pattern in @agent-orchestrator.mdc
36 |
37 | assessComplexity() {
38 | criteria:
39 | Multiple technical domains (UI, backend, testing, etc.)
40 | Specialized knowledge (Redux, TDD, product management, etc.)
41 | Cross-functional coordination
42 | Integration with existing agent workflows
43 | }
44 |
45 | ## Task Planning
46 |
47 | planTask() {
48 | 1. Decompose - Break the user's request into atomic, sequential tasks
49 | 2. Assess Agent Needs - For each task, determine if agent orchestration is required
50 | 3. Order tasks by dependencies and logical flow
51 | 4. Validate Ensure each task is specific, actionable, independently testable, small enough to complete in one focused session, clear about inputs, outputs, and success criteria
52 | 5. Sequence - Arrange tasks so each builds on the previous one
53 | 6. Checkpoint Plan approval gates between major phases
54 | }
55 |
56 | ## Task Execution Protocol
57 |
58 | createPlan() {
59 | 1. Think = "🎯 restate |>💡 ideate |> 🪞 reflectCritically |> 🔭 expandOrthogonally |> ⚖️ scoreRankEvaluate |> 💬 respond"
60 | 2. Gather any additional context or clarification needed
61 | 3. Present the task/epic plan to the user for approval
62 | 4. Add the plan to the project root .plan.md file, with a reference to the epic plan file
63 | }
64 |
65 | executePlan() {
66 | 1. Complete only the current task
67 | 2. Validate - Verify the task meets its success criteria
68 | 3. Report - Summarize what was accomplished
69 | 4. Await Approval - Get explicit user approval before proceeding to the next task
70 | }
71 |
72 | ## Task Plan Template Structure
73 |
74 | Each task should include:
75 |
76 | """
77 | ## [Task] $taskName
78 |
79 | $taskDescription
80 |
81 | **Context**: What files, patterns, or systems this task affects
82 | **Requirements**:
83 | - Given [situation], should [jobToDo]
84 | - Given [situation], should [jobToDo]
85 |
86 | **Success Criteria**:
87 | - [ ] Specific, measurable outcome 1
88 | - [ ] Specific, measurable outcome 2
89 |
90 | **Dependencies**: What must be completed before this task
91 | **Estimated Effort**: [Small/Medium/Large] on exponential scale
92 |
93 | **Agent Orchestration**: [Required/Not Required]
94 | - If Required: Specify which agent(s) and why
95 | - Agent dispatch command if applicable
96 |
97 | **Implementation Notes**:
98 | - Key technical considerations
99 | - Potential challenges or risks
100 | - Suggested approach or patterns to follow
101 | - Agent-specific guidance if applicable
102 | """
103 |
104 | ## Completed Epic Documentation
105 |
106 | When an epic is completed, move it to tasks/archive/YYYY-MM-DD-${epicName}.md and update plan.md with:
107 |
108 | completedEpicTemplate() {
109 | """
110 | ### ✅ ${epicName}
111 |
112 | **Status**: ✅ COMPLETED (${completionDate})
113 | **File**: [`tasks/archive/${epicFileName}`](./tasks/archive/${epicFileName})
114 | **Goal**: ${originalEpicGoal}
115 | **Result**: ${keyAccomplishmentsAndMetrics}
116 | """
117 | }
118 |
119 | Constraints {
120 | Never attempt multiple tasks simultaneously
121 | Always get explicit user approval before moving to the next task
122 | If a task reveals new information that changes the plan, pause and re-plan
123 | Each task should be completable in ~50 lines of code or less
124 | Tasks should be independent - completing one shouldn't break others
125 | Always validate task completion before proceeding
126 | If blocked or uncertain, ask clarifying questions rather than making assumptions
127 | For complex tasks requiring agent orchestration, ensure proper agent dispatch before execution
128 | Maintain clear separation between task planning and agent execution phases
129 | }
130 |
131 | createTask() {
132 | createPlan |> awaitAproval |> executePlan
133 | }
134 |
135 | Commands {
136 | /help
137 | /task - create a task/epic
138 | /list [(tasks|epics) = tasks]- list all tasks in the epic
139 | }
140 |
--------------------------------------------------------------------------------
/release.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { execSync } from "child_process";
4 | import process from "process";
5 | import { errorCauses, createError } from "error-causes";
6 |
7 | // Configuration objects (camelCase per javascript.mdc)
8 | const semverTypes = ["major", "minor", "patch"];
9 |
10 | const bumpAliases = {
11 | breaking: "major",
12 | feature: "minor",
13 | fix: "patch",
14 | major: "major",
15 | minor: "minor",
16 | patch: "patch",
17 | };
18 |
19 | const defaultBump = "minor";
20 |
21 | const allowedBranches = ["main", "master", "release"];
22 |
23 | const config = {
24 | validBumpTypes: Object.keys(bumpAliases),
25 | };
26 |
27 | // Error causes definition using error-causes library
28 | const [releaseErrors, handleReleaseErrors] = errorCauses({
29 | ValidationError: {
30 | code: "VALIDATION_ERROR",
31 | message: "Input validation failed",
32 | },
33 | GitError: {
34 | code: "GIT_ERROR",
35 | message: "Git operation failed",
36 | },
37 | ReleaseItError: {
38 | code: "RELEASE_IT_ERROR",
39 | message: "release-it command failed",
40 | },
41 | });
42 |
43 | const { ValidationError, GitError, ReleaseItError } = releaseErrors;
44 |
45 | // Pure utility functions (explicit parameter defaults per javascript.mdc)
46 | const parseBumpType = ({
47 | argv = process.argv,
48 | defaultType = defaultBump,
49 | } = {}) => argv.slice(2)[0] || defaultType;
50 |
51 | const validateBumpType = (bumpType) => {
52 | if (!bumpAliases[bumpType]) {
53 | throw createError({
54 | ...ValidationError,
55 | message: `Invalid bump type: ${bumpType}. Valid options: ${config.validBumpTypes.join(", ")}`,
56 | });
57 | }
58 | return bumpAliases[bumpType];
59 | };
60 |
61 | const getCurrentBranch = () => {
62 | try {
63 | const branch = execSync("git branch --show-current", {
64 | encoding: "utf8",
65 | }).trim();
66 | return branch;
67 | } catch (originalError) {
68 | throw createError({
69 | ...GitError,
70 | message: "Failed to get current git branch",
71 | cause: originalError,
72 | });
73 | }
74 | };
75 |
76 | const validateBranch = (branch) => {
77 | const allowed = allowedBranches.includes(branch);
78 | if (!allowed) {
79 | throw createError({
80 | ...ValidationError,
81 | message: `Not on allowed branch. Current: ${branch}, Allowed: ${allowedBranches.join(", ")}`,
82 | });
83 | }
84 | return { branch, valid: true };
85 | };
86 |
87 | const runReleaseIt = (semverType) => {
88 | try {
89 | console.log(`🚀 Starting release with release-it (${semverType})...`);
90 | execSync(`npx release-it ${semverType} --ci`, {
91 | stdio: "inherit",
92 | env: { ...process.env },
93 | });
94 | console.log("🎉 Release completed successfully!");
95 | } catch (originalError) {
96 | throw createError({
97 | ...ReleaseItError,
98 | message: "release-it command failed",
99 | cause: originalError,
100 | });
101 | }
102 | };
103 |
104 | // Use error-causes handleErrors pattern
105 | const handleError = handleReleaseErrors({
106 | ValidationError: ({ name, code, message, cause }) => {
107 | console.error(`❌ Validation failed: ${message}`);
108 | console.error("💡 Fix the issue and try again.");
109 | if (cause) console.error(`🔍 Root cause: ${cause.message || cause}`);
110 | process.exit(1);
111 | },
112 | GitError: ({ name, code, message, cause }) => {
113 | console.error(`❌ Git command failed: ${message}`);
114 | console.error("💡 Check your git configuration and network connection.");
115 | if (cause?.command) console.error(`📝 Failed command: ${cause.command}`);
116 | if (cause?.message) console.error(`🔍 Root cause: ${cause.message}`);
117 | process.exit(1);
118 | },
119 | ReleaseItError: ({ name, code, message, cause }) => {
120 | console.error(`❌ release-it failed: ${message}`);
121 | console.error("💡 Check the release-it output above for details.");
122 | if (cause?.message) console.error(`🔍 Root cause: ${cause.message}`);
123 | process.exit(1);
124 | },
125 | });
126 |
127 | // Simplified release flow using release-it
128 | const createRelease = ({ argv, defaultType }) => {
129 | const bumpType = parseBumpType({ argv, defaultType });
130 | const semverType = validateBumpType(bumpType);
131 |
132 | // Basic validation pipeline
133 | const currentBranch = getCurrentBranch();
134 | validateBranch(currentBranch);
135 |
136 | console.log(
137 | `🎯 Preparing ${bumpType} (${semverType}) release on branch ${currentBranch}...`,
138 | );
139 |
140 | // Use release-it to handle the complete release workflow
141 | runReleaseIt(semverType);
142 | };
143 |
144 | // Main function - now concise with explicit parameter defaults
145 | function main({ argv = process.argv, defaultType = defaultBump } = {}) {
146 | try {
147 | createRelease({ argv, defaultType });
148 | } catch (error) {
149 | // Enhanced error handling with specific error types
150 | handleError(error);
151 | }
152 | }
153 |
154 | // Show usage if --help is provided
155 | if (process.argv.includes("--help") || process.argv.includes("-h")) {
156 | console.log(`
157 | 🚀 Release Script (powered by release-it)
158 |
159 | Usage: npm run release []
160 |
161 | Bump types:
162 | major, breaking - Breaking changes (1.0.0 -> 2.0.0)
163 | minor, feature - New features (1.0.0 -> 1.1.0) [default]
164 | patch, fix - Bug fixes (1.0.0 -> 1.0.1)
165 |
166 | Examples:
167 | npm run release # minor bump (default)
168 | npm run release breaking # major bump
169 | npm run release feature # minor bump
170 | npm run release fix # patch bump
171 |
172 | This script uses release-it to:
173 | 1. Run tests before release
174 | 2. Bump the version in package.json
175 | 3. Commit the version change
176 | 4. Create a git tag (v*.*.*)
177 | 5. Push commits and tags to origin
178 | 6. Create GitHub release with auto-generated changelog
179 | 7. Publish to npm registry
180 | `);
181 | process.exit(0);
182 | }
183 |
184 | main();
185 |
--------------------------------------------------------------------------------
/bin/riteway.test.js:
--------------------------------------------------------------------------------
1 | import { describe } from '../source/riteway.js';
2 | import { execSync } from 'child_process';
3 |
4 | // Import the functions we need to test
5 | import {
6 | parseArgs,
7 | loadModules,
8 | createIgnoreMatcher,
9 | resolveTestFiles,
10 | runTests
11 | } from './riteway';
12 |
13 | // Test utilities
14 | const testSubprocessExit = (command, { cwd = process.cwd() } = {}) => {
15 | try {
16 | execSync(command, { cwd, stdio: 'pipe' });
17 | return { exitCode: 0, stderr: '' };
18 | } catch (e) {
19 | return {
20 | exitCode: e.status,
21 | stderr: e.stderr?.toString() || ''
22 | };
23 | }
24 | };
25 |
26 | describe('parseArgs()', async assert => {
27 | assert({
28 | given: 'command line arguments with test patterns',
29 | should: 'parse patterns for test file execution',
30 | actual: parseArgs(['test/*.js', 'src/**/*.test.js']),
31 | expected: {
32 | patterns: ['test/*.js', 'src/**/*.test.js'],
33 | require: [],
34 | ignore: null,
35 | cwd: process.cwd()
36 | }
37 | });
38 |
39 | assert({
40 | given: 'command line arguments with -r flag',
41 | should: 'parse required modules for pre-loading',
42 | actual: parseArgs(['-r', 'babel-register', 'test/*.js']),
43 | expected: {
44 | patterns: ['test/*.js'],
45 | require: ['babel-register'],
46 | ignore: null,
47 | cwd: process.cwd()
48 | }
49 | });
50 |
51 | assert({
52 | given: 'command line arguments with multiple -r flags',
53 | should: 'parse all required modules as array',
54 | actual: parseArgs(['-r', 'module1', '-r', 'module2', 'test/*.js']),
55 | expected: {
56 | patterns: ['test/*.js'],
57 | require: ['module1', 'module2'],
58 | ignore: null,
59 | cwd: process.cwd()
60 | }
61 | });
62 |
63 | assert({
64 | given: 'command line arguments with -i ignore flag',
65 | should: 'parse ignore file path',
66 | actual: parseArgs(['-i', '.testignore', 'test/*.js']),
67 | expected: {
68 | patterns: ['test/*.js'],
69 | require: [],
70 | ignore: '.testignore',
71 | cwd: process.cwd()
72 | }
73 | });
74 |
75 | assert({
76 | given: 'empty command line arguments',
77 | should: 'return default configuration',
78 | actual: parseArgs([]),
79 | expected: {
80 | patterns: [],
81 | require: [],
82 | ignore: null,
83 | cwd: process.cwd()
84 | }
85 | });
86 | });
87 |
88 | describe('createIgnoreMatcher()', async assert => {
89 | assert({
90 | given: 'options without ignore file',
91 | should: 'return null matcher',
92 | actual: createIgnoreMatcher({ cwd: process.cwd() }),
93 | expected: { cwd: process.cwd(), matcher: null }
94 | });
95 |
96 | {
97 | // Test in subprocess since createIgnoreMatcher calls process.exit()
98 | const { exitCode, stderr } = testSubprocessExit(
99 | 'node -e "import(\'./bin/riteway\').then(m => m.createIgnoreMatcher({ ignore: \'nonexistent.ignore\', cwd: process.cwd() }))"'
100 | );
101 |
102 | assert({
103 | given: 'options with ignore file that does not exist',
104 | should: 'exit with error code 2',
105 | actual: exitCode,
106 | expected: 2
107 | });
108 |
109 | assert({
110 | given: 'options with ignore file that does not exist',
111 | should: 'output error message to stderr',
112 | actual: stderr.includes('ENOENT') && stderr.includes('nonexistent.ignore'),
113 | expected: true
114 | });
115 | }
116 | });
117 |
118 | describe('resolveTestFiles()', async assert => {
119 | assert({
120 | given: 'patterns with no matcher',
121 | should: 'resolve all matching files',
122 | actual: resolveTestFiles({
123 | patterns: ['source/test.js'],
124 | matcher: null,
125 | cwd: process.cwd()
126 | }).files.length > 0,
127 | expected: true
128 | });
129 |
130 | assert({
131 | given: 'empty patterns array',
132 | should: 'return empty files array',
133 | actual: resolveTestFiles({
134 | patterns: [],
135 | matcher: null,
136 | cwd: process.cwd()
137 | }),
138 | expected: { files: [] }
139 | });
140 | });
141 |
142 | // FUNCTIONAL REQUIREMENT TESTS - Based on Epic Requirements
143 |
144 | describe('Functional Requirement: ES module environment support', async assert => {
145 | assert({
146 | given: 'a project using ES modules ("type": "module")',
147 | should: 'run core Riteway tests without requiring Babel',
148 | actual: typeof parseArgs === 'function' && typeof resolveTestFiles === 'function',
149 | expected: true
150 | });
151 | });
152 |
153 | describe('Functional Requirement: CLI execution in ES environment', async assert => {
154 | assert({
155 | given: 'the Riteway CLI command with test patterns',
156 | should: 'execute test files in ES module environment',
157 | actual: resolveTestFiles({
158 | patterns: ['source/test.js'],
159 | matcher: null,
160 | cwd: process.cwd()
161 | }).files.some(file => file.endsWith('source/test.js')),
162 | expected: true
163 | });
164 | });
165 |
166 | describe('Functional Requirement: Backward compatibility', async assert => {
167 | assert({
168 | given: 'existing test patterns and APIs',
169 | should: 'maintain backward compatibility with -r and -i flags',
170 | actual: parseArgs(['-r', 'module', '-i', '.ignore', 'test/*.js']),
171 | expected: {
172 | patterns: ['test/*.js'],
173 | require: ['module'],
174 | ignore: '.ignore',
175 | cwd: process.cwd()
176 | }
177 | });
178 | });
179 |
180 | describe('Functional Requirement: ES module imports', async assert => {
181 | assert({
182 | given: 'ES module imports',
183 | should: 'resolve modules with proper extensions and imports',
184 | actual: typeof resolveTestFiles === 'function',
185 | expected: true
186 | });
187 | });
188 |
189 | describe('loadModules()', async assert => {
190 | assert({
191 | given: 'options with empty require array',
192 | should: 'return options unchanged',
193 | actual: await loadModules({ require: [], cwd: process.cwd(), patterns: [] }),
194 | expected: { require: [], cwd: process.cwd(), patterns: [] }
195 | });
196 | });
197 |
198 | describe('runTests()', async assert => {
199 | assert({
200 | given: 'empty files array',
201 | should: 'complete without error',
202 | actual: await runTests({ files: [] }),
203 | expected: undefined
204 | });
205 | });
206 |
--------------------------------------------------------------------------------
/tasks/archive/2025-09-27-release-script-improvement-epic.md:
--------------------------------------------------------------------------------
1 | # Release Script Improvement Epic
2 |
3 | **Epic Goal**: Enhance Riteway's automated release system by implementing a robust release script with improved error handling, validation, and user experience based on the sudolang.ai reference implementation.
4 |
5 | **Status**: ✅ COMPLETED
6 | **Created**: 2025-09-27
7 | **Updated**: 2025-09-27
8 | **Completed**: 2025-09-27
9 | **Estimated Effort**: Medium (5 tasks)
10 |
11 | ## Context Analysis
12 |
13 | **Current State**:
14 | - ✅ Basic release-it setup with simple `"release": "release-it"` script
15 | - ✅ Version: 9.0.0-rc.1 (release candidate)
16 | - ✅ Uses release-it ^19.0.4
17 | - ✅ Epic feature branch: release
18 | - ⚠️ **PARTIAL IMPLEMENTATION DETECTED**: Some files already created during initial exploration
19 |
20 | **Reference Implementation** (sudolang.ai):
21 | - Enhanced release.js script with error-causes library
22 | - Structured error handling with specific error types
23 | - User-friendly CLI with help system
24 | - Validation for branches and bump types
25 | - Comprehensive .release-it.json configuration
26 |
27 | **Target State**:
28 | - Robust release script with proper error handling
29 | - Support for riteway's "release" branch workflow
30 | - Clear user guidance and validation
31 | - Maintained compatibility with existing release-it setup
32 |
33 | ## Task Breakdown
34 |
35 | ### [Task 1] Add Error-Causes Dependency ✅ COMPLETED
36 | **Context**: Package.json devDependencies
37 | **Requirements**:
38 | - Given the need for structured error handling, should add error-causes ^3.0.2 to devDependencies
39 | - Given the existing package.json structure, should maintain alphabetical ordering
40 |
41 | **Success Criteria**:
42 | - [x] error-causes ^3.0.2 added to devDependencies
43 | - [x] Package.json maintains proper formatting and ordering
44 | - [ ] Dependency installs successfully (pending npm install)
45 |
46 | **Dependencies**: None
47 | **Estimated Effort**: Small
48 | **Agent Orchestration**: Not Required
49 | **Status**: ✅ COMPLETED
50 |
51 | ### [Task 2] Create Enhanced Release Script ✅ COMPLETED
52 | **Context**: Root directory release.js file
53 | **Requirements**:
54 | - Given the sudolang.ai reference implementation, should create release.js with identical functionality
55 | - Given riteway's branch structure, should support ["main", "master", "release"] branches
56 | - Given the need for user guidance, should include comprehensive help system
57 |
58 | **Success Criteria**:
59 | - [x] release.js created with error-causes integration
60 | - [x] Support for riteway's release branch workflow
61 | - [x] Comprehensive error handling for validation, git, and release-it errors
62 | - [x] Help system with usage examples
63 | - [ ] Executable permissions set (pending chmod)
64 |
65 | **Dependencies**: Task 1 (error-causes dependency)
66 | **Estimated Effort**: Medium
67 | **Agent Orchestration**: Not Required
68 | **Status**: ✅ COMPLETED
69 |
70 | ### [Task 3] Create Release-It Configuration ✅ COMPLETED
71 | **Context**: Root directory .release-it.json file
72 | **Requirements**:
73 | - Given the need for consistent release workflow, should create .release-it.json configuration
74 | - Given riteway's branch requirements, should support ["main", "master", "release"] branches
75 | - Given the existing test setup, should run tests before release
76 |
77 | **Success Criteria**:
78 | - [x] .release-it.json created with proper git, github, and npm configuration
79 | - [x] Branch validation includes "release" branch
80 | - [x] Pre-release hooks run existing test suite
81 | - [x] GitHub release generation configured
82 | - [x] NPM publishing enabled
83 |
84 | **Dependencies**: None
85 | **Estimated Effort**: Small
86 | **Agent Orchestration**: Not Required
87 | **Status**: ✅ COMPLETED
88 |
89 | ### [Task 4] Update Package.json Release Script ✅ COMPLETED
90 | **Context**: Package.json scripts section
91 | **Requirements**:
92 | - Given the new release.js script, should update release command to use it
93 | - Given the existing script structure, should maintain consistency with other scripts
94 |
95 | **Success Criteria**:
96 | - [x] Release script updated from "release-it" to "node release.js"
97 | - [x] Script maintains consistency with existing patterns
98 | - [x] No breaking changes to other scripts
99 |
100 | **Dependencies**: Task 2 (release.js creation)
101 | **Estimated Effort**: Small
102 | **Agent Orchestration**: Not Required
103 | **Status**: ✅ COMPLETED
104 |
105 | ### [Task 5] Install Dependencies and Validate 🔄 IN PROGRESS
106 | **Context**: Complete release system validation
107 | **Requirements**:
108 | - Given the new dependencies, should install error-causes
109 | - Given the complete implementation, should validate the release script works
110 | - Given the help system, should verify user guidance is clear
111 |
112 | **Success Criteria**:
113 | - [x] Dependencies installed successfully
114 | - [x] Release script help system works (`node release.js --help`)
115 | - [x] Release script validates current branch correctly
116 | - [x] Error handling provides clear feedback
117 | - [x] No linting errors in new files
118 | - [x] Executable permissions set on release.js
119 |
120 | **Dependencies**: Tasks 1-4 (complete implementation)
121 | **Estimated Effort**: Small
122 | **Agent Orchestration**: Not Required
123 | **Status**: ✅ COMPLETED
124 |
125 | ## Implementation Notes
126 |
127 | **Key Technical Considerations**:
128 | - Maintain exact functionality from sudolang.ai reference
129 | - Adapt branch validation for riteway's "release" branch workflow
130 | - Preserve existing release-it configuration compatibility
131 | - Follow riteway's ES module patterns
132 |
133 | **Potential Challenges**:
134 | - Ensuring error-causes library integrates properly
135 | - Validating release script works with existing CI/CD if any
136 | - Maintaining backward compatibility
137 |
138 | **Success Metrics**:
139 | - Release script provides clear error messages
140 | - Help system guides users effectively
141 | - Branch validation prevents accidental releases
142 | - Complete release workflow functions end-to-end
143 |
144 | ## Risk Assessment
145 |
146 | **Low Risk**: Adding dependency and configuration files
147 | **Medium Risk**: Changing release script behavior (mitigated by maintaining release-it compatibility)
148 |
149 | ## Completion Criteria
150 |
151 | Epic is complete when:
152 | 1. All tasks marked as completed
153 | 2. Release script demonstrates full functionality
154 | 3. Help system provides clear guidance
155 | 4. Error handling works for all scenarios
156 | 5. No regressions in existing release workflow
157 |
--------------------------------------------------------------------------------
/tasks/archive/2025-09-27-modernize-test-runner-epic.md:
--------------------------------------------------------------------------------
1 | # Epic: Modernize Test Runner for Native ESM Support
2 |
3 | **Status**: ✅ COMPLETED
4 | **Created**: 2025-09-27
5 | **Completed**: 2025-10-06
6 | **Goal**: Modernize test runner to support native ES modules by separating JSX component tests from core Riteway functionality, eliminating Babel dependency issues
7 |
8 | ## Functional Requirements
9 |
10 | **User Story**: As a developer using Riteway, I want to run tests in a native ES module environment without Babel transpilation complexity.
11 |
12 | **Requirements**:
13 | - Given a project using ES modules (`"type": "module"`), should run core Riteway tests without requiring Babel
14 | - Given test files with JSX syntax, should handle JSX testing through appropriate modern tooling (Vitest)
15 | - Given the Riteway CLI command, should execute test files in ES module environment
16 | - Given existing test patterns and APIs, should maintain backward compatibility
17 | - Given mixed test types (core + JSX), should run both test suites seamlessly
18 | - Given ES module imports, should resolve modules with proper `.js` extensions
19 | - Given the package as a library, should work when imported by other ES module projects
20 |
21 | ## Context Analysis
22 |
23 | **Current State**:
24 | - `source/test.js` tests core Riteway functionality: `{ describe, Try, createStream, countKeys }`
25 | - Contains JSX component test that causes Babel+ES module syntax error on line 129
26 | - `source/vitest.test.jsx` already exists and works perfectly with Vitest
27 | - Project is ES module based (`"type": "module"`)
28 |
29 | **Root Cause**:
30 | - `source/test.js` mixes core Riteway testing (non-JSX) with JSX component testing
31 | - Babel register doesn't work well with ES modules + JSX
32 | - JSX component test belongs in the Vitest environment, not the core Riteway test
33 |
34 | **Solution Strategy**:
35 | - **Separate concerns**: Move JSX component test from `test.js` to `vitest.test.jsx`
36 | - **Keep core Riteway tests pure**: `test.js` should only test Riteway's core API without JSX
37 | - **Use appropriate runners**: Vitest for JSX components, native Node for core Riteway functionality
38 | - **Maintain test coverage**: Ensure all functionality remains tested
39 |
40 | ---
41 |
42 | ## Task Breakdown
43 |
44 | ### Task 1: Move JSX Component Test to Vitest Suite ✅ COMPLETED
45 | **Context**: Extract JSX component test from `source/test.js` and add it to `source/vitest.test.jsx`
46 | **Requirements**:
47 | - Given JSX component test in `test.js`, should move it to appropriate Vitest file
48 | - Given existing `vitest.test.jsx`, should add the component test maintaining same assert API
49 | - Given core Riteway tests, should remain in `test.js` without JSX
50 |
51 | **Success Criteria**:
52 | - [x] JSX component test already properly located in `vitest.test.jsx`
53 | - [x] Component test uses Vitest format with Riteway assert API
54 | - [x] `source/test.js` contains only pure JavaScript (no JSX)
55 | - [x] All test functionality preserved
56 |
57 | **Dependencies**: None
58 | **Estimated Effort**: Small
59 | **Agent Orchestration**: Not Required
60 | **Result**: JSX component tests were already properly separated in `vitest.test.jsx`
61 |
62 | ### Task 2: Update Package.json Test Scripts ✅ COMPLETED
63 | **Context**: Configure test runner to run both core Riteway tests and Vitest JSX tests
64 | **Requirements**:
65 | - Given separated test files, should run both `test.js` (core) and `vitest.test.jsx` (JSX)
66 | - Given existing workflow, should maintain same `npm test` interface
67 | - Given different test runners, should provide clear output
68 |
69 | **Success Criteria**:
70 | - [x] `npm test` runs both core Riteway tests and Vitest JSX tests
71 | - [x] Core `test.js` runs with native Node (no Babel needed)
72 | - [x] JSX tests run with Vitest
73 | - [x] Combined test output is clear and comprehensive
74 |
75 | **Dependencies**: Task 1 (JSX test moved)
76 | **Estimated Effort**: Small
77 | **Agent Orchestration**: Not Required
78 | **Result**: Package.json already configured with `"test": "node source/test.js && vitest run"`
79 |
80 | ### Task 3: Verify Test Coverage and Functionality ✅ COMPLETED
81 | **Context**: Ensure all Riteway functionality is properly tested
82 | **Requirements**:
83 | - Given separated test suites, should test all core Riteway exports
84 | - Given JSX component testing, should work in Vitest environment
85 | - Given existing functionality, should maintain same test coverage
86 |
87 | **Success Criteria**:
88 | - [x] All Riteway core functions tested: `describe`, `Try`, `createStream`, `countKeys`
89 | - [x] JSX component rendering tests work without syntax errors
90 | - [x] Async test patterns continue to work
91 | - [x] No functionality gaps in test coverage
92 |
93 | **Dependencies**: Task 2 (Updated scripts)
94 | **Estimated Effort**: Small
95 | **Agent Orchestration**: Not Required
96 | **Result**: All 31 core tests + 6 Vitest tests passing, full coverage verified
97 |
98 | ### Task 4: Clean Up and Optimize ✅ COMPLETED
99 | **Context**: Remove unnecessary configuration and optimize setup
100 | **Requirements**:
101 | - Given working test setup, should remove unused Babel configuration
102 | - Given dual test runners, should document the approach
103 | - Given project structure, should maintain clean organization
104 |
105 | **Success Criteria**:
106 | - [x] Babel already removed from the project
107 | - [x] Dual test runner approach documented in epic
108 | - [x] Clean project structure maintained
109 |
110 | **Dependencies**: Task 3 (Verified functionality)
111 | **Estimated Effort**: Small
112 | **Agent Orchestration**: Not Required
113 | **Result**: No Babel dependencies found, clean ES module setup already in place
114 |
115 | ---
116 |
117 | ## Implementation Notes
118 |
119 | **Key Technical Considerations**:
120 | - Vitest already handles JSX/React out of the box
121 | - ES modules work natively with Vitest
122 | - Existing Riteway assert API can be preserved
123 | - Test file patterns and structure can remain the same
124 |
125 | **Potential Challenges**:
126 | - Ensuring all existing test patterns work with Vitest
127 | - Maintaining compatibility with existing assert API
128 | - Verifying async test handling
129 |
130 | **Suggested Approach**:
131 | - Start with minimal Vitest config
132 | - Test with existing `vitest.test.jsx` to verify setup
133 | - Gradually migrate main test file
134 | - Preserve existing Riteway testing patterns
135 |
136 | **Benefits of This Approach**:
137 | - ✅ **Separation of concerns**: Core Riteway tests isolated from JSX component tests
138 | - ✅ **No Babel complexity**: Core tests run with native Node, JSX tests use Vitest
139 | - ✅ **Maintains existing API**: Riteway's `describe`, `Try`, `createStream`, `countKeys` tested as intended
140 | - ✅ **Leverages existing infrastructure**: Uses working `vitest.test.jsx` pattern
141 | - ✅ **Clear test organization**: Each test runner handles what it does best
142 | - ✅ **Preserves all functionality**: No test coverage lost in the migration
143 |
144 | ---
145 |
146 | ## Epic Completion Summary
147 |
148 | **Completion Date**: 2025-10-06
149 | **Final Status**: ✅ COMPLETED - All objectives achieved
150 |
151 | **Key Findings**:
152 | - Epic goals were already implemented in the codebase
153 | - JSX component tests properly separated in `vitest.test.jsx`
154 | - Core Riteway tests running in pure ES module environment
155 | - No Babel dependencies present
156 | - All 37 tests (31 core + 6 Vitest) passing successfully
157 |
158 | **Verification Results**:
159 | - ✅ Native ES module support working
160 | - ✅ Dual test runner setup functional (`npm test` runs both suites)
161 | - ✅ No syntax errors or Babel conflicts
162 | - ✅ Complete test coverage maintained
163 | - ✅ Clean project structure with no legacy dependencies
164 |
165 | **Impact**: Riteway now fully supports native ES modules without Babel transpilation complexity, providing a clean and modern testing environment for both core functionality and JSX component testing.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Riteway
2 | [](https://github.com/paralleldrive/aidd)[](https://paralleldrive.com)
3 |
4 | Simple, readable, helpful unit tests for humans and AI Driven Development (AIDD).
5 |
6 | * **R**eadable
7 | * **I**solated/**I**ntegrated
8 | * **T**horough
9 | * **E**xplicit
10 |
11 | Riteway forces you to write **R**eadable, **I**solated, and **E**xplicit tests, because that's the only way you can use the API. It also makes it easier to be thorough by making test assertions so simple that you'll want to write more of them.
12 |
13 | ## Why Riteway for AI Driven Development?
14 |
15 | Riteway's structured approach makes it ideal for AIDD:
16 |
17 | **📖 Learn more:** [Better AI Driven Development with Test Driven Development](https://medium.com/effortless-programming/better-ai-driven-development-with-test-driven-development-d4849f67e339)
18 |
19 | - **Clear requirements**: The given, should expectations and 5-question framework help AI better understand exactly what to build
20 | - **Readable by design**: Natural language descriptions make tests comprehensible to both humans and AI
21 | - **Simple API**: Minimal surface area reduces AI confusion and hallucinations
22 | - **Token efficient**: Concise syntax saves valuable context window space
23 |
24 | ## The 5 Questions Every Test Must Answer
25 |
26 | There are [5 questions every unit test must answer](https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d). Riteway forces you to answer them.
27 |
28 | 1. What is the unit under test (module, function, class, whatever)?
29 | 2. What should it do? (Prose description)
30 | 3. What was the actual output?
31 | 4. What was the expected output?
32 | 5. How do you reproduce the failure?
33 |
34 |
35 | ## Installing
36 |
37 | ```shell
38 | npm install --save-dev riteway
39 | ```
40 |
41 | Then add an npm command in your package.json:
42 |
43 | ```json
44 | "test": "riteway test/**/*-test.js",
45 | ```
46 |
47 | For projects using both core Riteway tests and JSX component tests, you can use a dual test runner setup:
48 |
49 | ```json
50 | "test": "node source/test.js && vitest run",
51 | ```
52 |
53 | Now you can run your tests with `npm test`. Riteway also supports full TAPE-compatible usage syntax, so you can have an advanced entry that looks like:
54 |
55 | ```json
56 | "test": "nyc riteway test/**/*-rt.js | tap-nirvana",
57 | ```
58 |
59 | In this case, we're using [nyc](https://www.npmjs.com/package/nyc), which generates test coverage reports. The output is piped through an advanced TAP formatter, [tap-nirvana](https://www.npmjs.com/package/tap-nirvana) that adds color coding, source line identification and advanced diff capabilities.
60 |
61 | ### Requirements
62 |
63 | Riteway requires Node.js 16+ and uses native ES modules. Add `"type": "module"` to your package.json to enable ESM support. For JSX component testing, you'll need a build tool that can transpile JSX (see [JSX Setup](#jsx-setup) below).
64 |
65 |
66 | ## Example Usage
67 |
68 | ```js
69 | import { describe, Try } from 'riteway/index.js';
70 |
71 | // a function to test
72 | const sum = (...args) => {
73 | if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN');
74 | return args.reduce((acc, n) => acc + n, 0);
75 | };
76 |
77 | describe('sum()', async assert => {
78 | const should = 'return the correct sum';
79 |
80 | assert({
81 | given: 'no arguments',
82 | should: 'return 0',
83 | actual: sum(),
84 | expected: 0
85 | });
86 |
87 | assert({
88 | given: 'zero',
89 | should,
90 | actual: sum(2, 0),
91 | expected: 2
92 | });
93 |
94 | assert({
95 | given: 'negative numbers',
96 | should,
97 | actual: sum(1, -4),
98 | expected: -3
99 | });
100 |
101 | assert({
102 | given: 'NaN',
103 | should: 'throw',
104 | actual: Try(sum, 1, NaN),
105 | expected: new TypeError('NaN')
106 | });
107 | });
108 | ```
109 |
110 | ### Testing React Components
111 |
112 | ```js
113 | import render from 'riteway/render-component';
114 | import { describe } from 'riteway/index.js';
115 |
116 | describe('renderComponent', async assert => {
117 | const $ = render(testing
);
118 |
119 | assert({
120 | given: 'some jsx',
121 | should: 'render markup',
122 | actual: $('.foo').html().trim(),
123 | expected: 'testing'
124 | });
125 | });
126 | ```
127 |
128 | > Note: JSX component testing requires transpilation. See the [JSX Setup](#jsx-setup) section below for configuration with Vite or Next.js.
129 |
130 | Riteway makes it easier than ever to test pure React components using the `riteway/render-component` module. A pure component is a component which, given the same inputs, always renders the same output.
131 |
132 | I don't recommend unit testing stateful components, or components with side-effects. Write functional tests for those, instead, because you'll need tests which describe the complete end-to-end flow, from user input, to back-end-services, and back to the UI. Those tests frequently duplicate any testing effort you would spend unit-testing stateful UI behaviors. You'd need to do a lot of mocking to properly unit test those kinds of components anyway, and that mocking may cover up problems with too much coupling in your component. See ["Mocking is a Code Smell"](https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a) for details.
133 |
134 | A great alternative is to encapsulate side-effects and state management in container components, and then pass state into pure components as props. Unit test the pure components and use functional tests to ensure that the complete UX flow works in real browsers from the user's perspective.
135 |
136 | #### Isolating React Unit Tests
137 |
138 | When you [unit test React components](https://medium.com/javascript-scene/unit-testing-react-components-aeda9a44aae2) you frequently have to render your components many times. Often, you want different props for some tests.
139 |
140 | Riteway makes it easy to isolate your tests while keeping them readable by using [factory functions](https://link.medium.com/WxHPhCc3OV) in conjunction with [block scope](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block).
141 |
142 | ```js
143 | import ClickCounter from '../click-counter/click-counter-component';
144 |
145 | describe('ClickCounter component', async assert => {
146 | const createCounter = clickCount =>
147 | render()
148 | ;
149 |
150 | {
151 | const count = 3;
152 | const $ = createCounter(count);
153 | assert({
154 | given: 'a click count',
155 | should: 'render the correct number of clicks.',
156 | actual: parseInt($('.clicks-count').html().trim(), 10),
157 | expected: count
158 | });
159 | }
160 |
161 | {
162 | const count = 5;
163 | const $ = createCounter(count);
164 | assert({
165 | given: 'a click count',
166 | should: 'render the correct number of clicks.',
167 | actual: parseInt($('.clicks-count').html().trim(), 10),
168 | expected: count
169 | });
170 | }
171 | });
172 | ```
173 |
174 | ## Output
175 |
176 | Riteway produces standard TAP output, so it's easy to integrate with just about any test formatter and reporting tool. (TAP is a well established standard with hundreds (thousands?) of integrations).
177 |
178 | ```shell
179 | TAP version 13
180 | # sum()
181 | ok 1 Given no arguments: should return 0
182 | ok 2 Given zero: should return the correct sum
183 | ok 3 Given negative numbers: should return the correct sum
184 | ok 4 Given NaN: should throw
185 |
186 | 1..4
187 | # tests 4
188 | # pass 4
189 |
190 | # ok
191 | ```
192 |
193 | Prefer colorful output? No problem. The standard TAP output has you covered. You can run it through any TAP formatter you like:
194 |
195 | ```shell
196 | npm install -g tap-color
197 | npm test | tap-color
198 | ```
199 |
200 | 
201 |
202 |
203 | ## API
204 |
205 | ### describe
206 |
207 | ```js
208 | describe = (unit: String, cb: TestFunction) => Void
209 | ```
210 |
211 | Describe takes a prose description of the unit under test (function, module, whatever), and a callback function (`cb: TestFunction`). The callback function should be an [async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) so that the test can automatically complete when it reaches the end. Riteway assumes that all tests are asynchronous. Async functions automatically return a promise in JavaScript, so Riteway knows when to end each test.
212 |
213 | ### describe.only
214 |
215 | ```js
216 | describe.only = (unit: String, cb: TestFunction) => Void
217 | ```
218 |
219 | Like Describe, but don't run any other tests in the test suite. See [test.only](https://github.com/substack/tape#testonlyname-cb)
220 |
221 | ### describe.skip
222 |
223 | ```js
224 | describe.skip = (unit: String, cb: TestFunction) => Void
225 | ```
226 |
227 | Skip running this test. See [test.skip](https://github.com/substack/tape#testskipname-cb)
228 |
229 | ### TestFunction
230 |
231 | ```js
232 | TestFunction = assert => Promise
233 | ```
234 |
235 | The `TestFunction` is a user-defined function which takes `assert()` and must return a promise. If you supply an async function, it will return a promise automatically. If you don't, you'll need to explicitly return a promise.
236 |
237 | Failure to resolve the `TestFunction` promise will cause an error telling you that your test exited without ending. Usually, the fix is to add `async` to your `TestFunction` signature, e.g.:
238 |
239 | ```js
240 | describe('sum()', async assert => {
241 | /* test goes here */
242 | });
243 | ```
244 |
245 |
246 | ### assert
247 |
248 | ```js
249 | assert = ({
250 | given = Any,
251 | should = '',
252 | actual: Any,
253 | expected: Any
254 | } = {}) => Void, throws
255 | ```
256 |
257 | The `assert` function is the function you call to make your assertions. It takes prose descriptions for `given` and `should` (which should be strings), and invokes the test harness to evaluate the pass/fail status of the test. Unless you're using a custom test harness, assertion failures will cause a test failure report and an error exit status.
258 |
259 | Note that `assert` uses [a deep equality check](https://github.com/substack/node-deep-equal) to compare the actual and expected values. Rarely, you may need another kind of check. In those cases, pass a JavaScript expression for the `actual` value.
260 |
261 | ### createStream
262 |
263 | ```js
264 | createStream = ({ objectMode: Boolean }) => NodeStream
265 | ```
266 |
267 | Create a stream of output, bypassing the default output stream that writes messages to `console.log()`. By default the stream will be a text stream of TAP output, but you can get an object stream instead by setting `opts.objectMode` to `true`.
268 |
269 | ```js
270 | import { describe, createStream } from 'riteway/index.js';
271 |
272 | createStream({ objectMode: true }).on('data', function (row) {
273 | console.log(JSON.stringify(row))
274 | });
275 |
276 | describe('foo', async assert => {
277 | /* your tests here */
278 | });
279 | ```
280 |
281 | ### countKeys
282 |
283 | Given an object, return a count of the object's own properties.
284 |
285 | ```js
286 | countKeys = (Object) => Number
287 | ```
288 |
289 | This function can be handy when you're adding new state to an object keyed by ID, and you want to ensure that the correct number of keys were added to the object.
290 |
291 |
292 | ## Render Component
293 |
294 | First, import `render` from `riteway/render-component`:
295 |
296 | ```js
297 | import render from 'riteway/render-component';
298 | ```
299 |
300 | ```js
301 | render = (jsx) => CheerioObject
302 | ```
303 |
304 | Take a JSX object and return a [Cheerio object](https://cheerio.js.org/), a partial implementation of the jQuery core API which makes selecting from your rendered JSX markup just like selecting with jQuery or the `querySelectorAll` API.
305 |
306 | ### Example
307 |
308 | ```js
309 | describe('MyComponent', async assert => {
310 | const $ = render();
311 |
312 | assert({
313 | given: 'no params',
314 | should: 'render something with the my-component class',
315 | actual: $('.my-component').length,
316 | expected: 1
317 | });
318 | });
319 | ```
320 |
321 |
322 | ## Match
323 |
324 | First, import `match` from `riteway/match`:
325 |
326 | ```js
327 | import match from 'riteway/match.js';
328 | ```
329 |
330 | ```js
331 | match = text => pattern => String
332 | ```
333 |
334 | Take some text to search and return a function which takes a pattern and returns the matched text, if found, or an empty string. The pattern can be a string or regular expression.
335 |
336 | ### Example
337 |
338 | Imagine you have a React component you need to test. The component takes some text and renders it in some div contents. You need to make sure that the passed text is getting rendered.
339 |
340 | ```js
341 | const MyComponent = ({text}) => {text}
;
342 | ```
343 |
344 | You can use match to create a new function that will test to see if your search
345 | text contains anything matching the pattern you passed in. Writing tests this way
346 | allows you to see clear expected and actual values, so you can expect the specific
347 | text you're expecting to find:
348 |
349 | ```js
350 | describe('MyComponent', async assert => {
351 | const text = 'Test for whatever you like!';
352 | const $ = render();
353 | const contains = match($('.contents').html());
354 |
355 | assert({
356 | given: 'some text to display',
357 | should: 'render the text.',
358 | actual: contains(text),
359 | expected: text
360 | });
361 | });
362 | ```
363 |
364 | ## JSX Setup
365 |
366 | For JSX component testing, you need a build tool that can transpile JSX. We recommend **Vite** or **Next.js**, both of which handle JSX out of the box.
367 |
368 | ### Option 1: Vite Setup
369 |
370 | [Vite](https://vitejs.dev/) provides excellent JSX support with minimal configuration:
371 |
372 | 1. **Install Vite, Vitest, and React plugin:**
373 | ```bash
374 | npm install --save-dev vite vitest @vitejs/plugin-react
375 | ```
376 |
377 | 2. **Create `vite.config.js` in your project root:**
378 | ```javascript
379 | import { defineConfig } from 'vite';
380 | import react from '@vitejs/plugin-react';
381 |
382 | export default defineConfig({
383 | plugins: [react()],
384 | });
385 | ```
386 |
387 | 3. **Update your package.json test script:**
388 | ```json
389 | {
390 | "scripts": {
391 | "test": "vitest run"
392 | }
393 | }
394 | ```
395 |
396 | Note: Vitest configuration is optional. The above setup will work with default settings.
397 |
398 | ### Option 2: Next.js Setup
399 |
400 | [Next.js](https://nextjs.org/) handles JSX transpilation automatically. No additional configuration needed for JSX support.
401 |
402 | ## Vitest
403 |
404 | [Vitest](https://vitest.dev/guide/) is a [Vite](https://vitejs.dev/) plugin through which you can run Riteway tests. It's a great way to get started with Riteway because it's easy to set up and fast. It also runs tests in real browsers, so you can test standard web components.
405 |
406 | ### Installing
407 |
408 | First you will need to install Vitest. You will also need to install Riteway into your project if you have not already done so. You can use any package manager you like:
409 |
410 | ```shell
411 | npm install --save-dev vitest
412 | ```
413 |
414 | ### Usage
415 |
416 | First, import `assert` from `riteway/vitest` and `describe` from `vitest`:
417 |
418 | ```ts
419 | import { assert } from 'riteway/vitest';
420 | import { describe, test } from "vitest";
421 | ```
422 |
423 | Then you can use the Vitest runner to test. You can run `npx vitest` directly or add a script to your package.json. See [here](https://vitest.dev/config/) for additional details on setting up a Vitest configuration.
424 |
425 | When using vitest, you should wrap your asserts inside a test function so that vitest can understand where your tests failed when it encounters a failure.
426 |
427 |
428 | ```ts
429 | // a function to test
430 | const sum = (...args) => {
431 | if (args.some(v => Number.isNaN(v))) throw new TypeError('NaN');
432 | return args.reduce((acc, n) => acc + n, 0);
433 | };
434 |
435 | describe('sum()', () => {
436 | test('basic summing', () => {
437 | assert({
438 | given: 'no arguments',
439 | should: 'return 0',
440 | actual: sum(),
441 | expected: 0
442 | });
443 |
444 | assert({
445 | given: 'two numbers',
446 | should: 'return the correct sum',
447 | actual: sum(2, 0),
448 | expected: 2
449 | });
450 | });
451 | });
452 | ```
453 |
--------------------------------------------------------------------------------