Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7c14302
feat(commands): add /rn-component scaffold command
eumaninho54 Apr 24, 2026
23ee45d
fix(rn-component): remove useMemo from useStyles template
eumaninho54 Apr 24, 2026
f82f234
fix(rn-component): remove invented stubs from ViewModel template
eumaninho54 Apr 24, 2026
7dea4e1
fix(rn-component): remove invented fields from IUseViewModelReturn te…
eumaninho54 Apr 24, 2026
8d1f369
fix(rn-component): fix collision check bash bugs in SKILL.md
eumaninho54 Apr 24, 2026
f614285
fix(rn-component): apply space check to interactively-supplied --path
eumaninho54 Apr 24, 2026
6eb2f29
fix(rn-component): make services and library index barrels
eumaninho54 Apr 24, 2026
f7889eb
feat(rn-component): add example files to services and library
eumaninho54 Apr 24, 2026
9e09cd2
fix(rn-component): rename IComponentNameProps template to use {{Compo…
eumaninho54 Apr 24, 2026
1f8c2cc
fix(rn-component): use import type for all interface imports
eumaninho54 Apr 24, 2026
a67a0b6
docs: add JSX conditional rendering style guide
eumaninho54 Apr 25, 2026
cc79834
docs: add branch naming conventions
eumaninho54 Apr 25, 2026
44480c0
docs(git-workflow): remove dev branch convention
eumaninho54 Apr 25, 2026
6f40401
ci(deploy): improve release workflow order and error handling
eumaninho54 Apr 27, 2026
993f8e1
docs(commit): broaden skill trigger description
eumaninho54 Apr 27, 2026
d1aac4a
docs(rules): extract conventional commits format
eumaninho54 Apr 30, 2026
aa2cecc
refactor(commit): remove duplicate format documentation
eumaninho54 Apr 30, 2026
c90a415
refactor(feature): reference workflow and commit rules
eumaninho54 Apr 30, 2026
d27e5e6
refactor(commands): add user-invocable frontmatter
eumaninho54 Apr 30, 2026
d049146
docs(rules): document user-invocable frontmatter requirement
eumaninho54 Apr 30, 2026
8379f18
Merge pull request #19 from Salve-Software/feat/rn-component-scaffold
eumaninho54 Apr 30, 2026
7b7fb02
Merge branch 'feat/rn-component-scaffold' into release/v0.2.0
eumaninho54 Apr 30, 2026
dc15216
chore(release): bump version to 0.2.0
github-actions[bot] Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
echo "TAG=v$VERSION" >> "$GITHUB_ENV"

- name: Checkout PR branch
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: ${{ env.PR_BRANCH }}
fetch-depth: 0
Expand Down Expand Up @@ -108,9 +108,16 @@ jobs:
run: |
git push origin HEAD:${{ env.PR_BRANCH }}

- name: Create git tag
- name: Merge PR
run: |
gh pr merge ${{ env.PR_NUMBER }} \
--merge \
--repo ${{ github.repository }}

- name: Tag merge commit on main
run: |
git tag "${{ env.TAG }}"
git fetch origin main
git tag "${{ env.TAG }}" origin/main
git push origin "${{ env.TAG }}"

- name: Create GitHub Release
Expand All @@ -120,15 +127,9 @@ jobs:
--generate-notes \
--repo ${{ github.repository }}

- name: Merge PR
run: |
gh pr merge ${{ env.PR_NUMBER }} \
--merge \
--repo ${{ github.repository }}

- name: Delete release branch
run: |
git push origin --delete "${{ env.PR_BRANCH }}"
git push origin --delete "${{ env.PR_BRANCH }}" || true

- name: Post success comment
if: success()
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@src/rules/skills-format.md
@src/rules/git-workflow.md
@src/rules/jsx-style.md

## Using skills

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@salve-software/ai-workers",
"version": "0.1.2",
"version": "0.2.0",
"description": "Central package for AI configurations and Claude Code rules.",
"scripts": {
"postinstall": "bash scripts/setup.sh"
Expand Down
23 changes: 7 additions & 16 deletions src/commands/feature/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
name: feature
description: This skill should be used when the user asks to "build a feature", "implement a feature", "develop a feature", or wants to autonomously develop, review, and ship a complete feature following the PDCA cycle.
user-invocable: true
argument-hint: <feature description>
allowed-tools: [Bash, Read, Glob, Grep, Agent]
model: sonnet
Expand Down Expand Up @@ -87,23 +88,13 @@ Your job is to implement a feature exactly as described in the plan below.

## Instructions

1. Follow the /branch command logic to create the branch (feat/, fix/, dev/) based on the feature type.
Read `src/rules/git-workflow.md` for branch naming rules.
Read `src/rules/conventional-commits.md` for commit types, format, and rules.

1. Create the branch following the branch naming rules from `git-workflow.md`.
2. Implement each step from the plan sequentially.
3. After each logical implementation chunk, follow the /commit command logic to commit (Conventional Commits, imperative, max 72 chars).
4. Co-author line on every commit: Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5. After all implementation is done, check if any test file was created or modified. If no test was touched, note it in your final report.

## /branch logic
- feat/ β€” new feature
- fix/ β€” bug fix
- dev/ β€” project/umbrella branch
- release/ β€” release, always with vX.X.X
- Lowercase, kebab-case, 2–4 words

## /commit logic
- Run git add <specific files> (never git add . or git add -A)
- Commit with HEREDOC format including co-author
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, cleanup, remove
3. After each logical implementation chunk, commit following `conventional-commits.md`. Co-author line on every commit: Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4. After all implementation is done, check if any test file was created or modified. If no test was touched, note it in your final report.

Do not push. Do not open a PR.

Expand Down
1 change: 1 addition & 0 deletions src/commands/land/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
name: land
description: Use when the user asks to create a branch and commit, or create a branch + commit + PR together. Orchestrates /branch and /commit while sharing the git diff context to avoid redundant reads. After committing, if the user also asked for a PR, invoke /pr.
user-invocable: true
argument-hint: [optional: branch name or description]
allowed-tools: [Bash, Read, Agent]
---
Expand Down
96 changes: 96 additions & 0 deletions src/commands/rn-component/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
name: rn-component
description: Scaffolds a React Native component using the Layered Hook Architecture (View / ViewModel / Styles / Animated / Services / Library).
user-invocable: true
argument-hint: --name <ComponentName> [--path <output-dir>]
allowed-tools: Bash, Read, Write
model: haiku
---

## Pre-flight β€” Parse arguments

Extract `--name` and `--path` values from `$ARGUMENTS`.

- If `--name` is missing: stop and print `⚠️ Missing --name. Usage: /rn-component --name MyButton --path src/components`
- If `--name` is not valid PascalCase (`^[A-Z][A-Za-z0-9]*$`): stop and print `⚠️ Invalid component name. Must be PascalCase (e.g. MyButton).`
- If `--path` is missing: ask the user `Where should the component be created? Provide the output directory path:` and wait for their answer before continuing.
- After `--path` is resolved (from arguments or from the user): if it contains spaces, stop and print `⚠️ Path must not contain spaces. Please provide a single-token path.`

## Derive variables

- `ComponentName` = value of `--name` (PascalCase)
- `componentName` = `ComponentName` with first letter lowercased (camelCase)
- `outputDir` = `<path>/<ComponentName>` β€” resolve `--path` to an absolute path if relative (use `pwd` to resolve)

## Collision check

Run via Bash:
Comment on lines +23 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

The command only validates that --path does not contain spaces, but it does not restrict the path to the current project directory. A user could provide a path like ../../etc or /tmp, and the command would resolve it and create files outside the intended project scope. While the user invokes this command intentionally, an attacker-controlled instruction could be injected via a prompt injection attack (e.g., "run /rn-component with path ../../ssh to overwrite authorized_keys"). The collision check only prevents overwriting if the exact final directory already exists, but mkdir -p will create intermediate directories in sensitive locations. Adding validation that the resolved absolute path starts with the project root would mitigate this risk.

Code Suggestion:

- After path resolution: verify that the resolved absolute path starts with the current project root (e.g., `pwd`). If not, abort with an error message.

```bash
if [ -d "<outputDir>" ]; then echo "EXISTS"; else echo "OK"; fi
```

If the result is `EXISTS`: stop and print `⚠️ <outputDir> already exists. Aborting to avoid overwrite.`

## Architecture summary

Read `references/architecture.md` (path relative to this SKILL.md file), then print a 3-line summary to the user:
- Layer overview (View / ViewModel / Styles / Animation / Services / Library)
- Type convention (I prefix for interfaces, barrel index.ts in every export folder)
- Barrel cycle rule (deep relative imports only, never ../..)

## Create directory tree

Run via Bash:
```bash
mkdir -p \
<outputDir>/types \
<outputDir>/hooks/use<ComponentName>ViewModel/types \
<outputDir>/hooks/useReanimatedStyles/types \
<outputDir>/services \
<outputDir>/library
```

## Generate files

For each template below:
1. Read the template file (path relative to this SKILL.md).
2. Replace every occurrence of `{{ComponentName}}` with the value of `ComponentName`.
3. Replace every occurrence of `{{componentName}}` with the value of `componentName`.
4. Write the result to the target path.

| Template (relative to this file) | Target |
|-----------------------------------|--------|
| `references/templates/index.tsx.tmpl` | `<outputDir>/index.tsx` |
| `references/templates/styles.ts.tmpl` | `<outputDir>/styles.ts` |
| `references/templates/types/I{{ComponentName}}Props.ts.tmpl` | `<outputDir>/types/I<ComponentName>Props.ts` |
| `references/templates/types/index.ts.tmpl` | `<outputDir>/types/index.ts` |
| `references/templates/hooks/index.ts.tmpl` | `<outputDir>/hooks/index.ts` |
| `references/templates/hooks/useComponentNameViewModel/index.ts.tmpl` | `<outputDir>/hooks/use<ComponentName>ViewModel/index.ts` |
| `references/templates/hooks/useComponentNameViewModel/types/IUseComponentNameViewModelReturn.ts.tmpl` | `<outputDir>/hooks/use<ComponentName>ViewModel/types/IUse<ComponentName>ViewModelReturn.ts` |
| `references/templates/hooks/useComponentNameViewModel/types/index.ts.tmpl` | `<outputDir>/hooks/use<ComponentName>ViewModel/types/index.ts` |
| `references/templates/hooks/useReanimatedStyles/index.ts.tmpl` | `<outputDir>/hooks/useReanimatedStyles/index.ts` |
| `references/templates/hooks/useReanimatedStyles/types/IUseReanimatedStylesProps.ts.tmpl` | `<outputDir>/hooks/useReanimatedStyles/types/IUseReanimatedStylesProps.ts` |
| `references/templates/hooks/useReanimatedStyles/types/index.ts.tmpl` | `<outputDir>/hooks/useReanimatedStyles/types/index.ts` |
| `references/templates/services/index.ts.tmpl` | `<outputDir>/services/index.ts` |
| `references/templates/services/fetch{{ComponentName}}Data.ts.tmpl` | `<outputDir>/services/fetch<ComponentName>Data.ts` |
| `references/templates/library/index.ts.tmpl` | `<outputDir>/library/index.ts` |
| `references/templates/library/format{{ComponentName}}Label.ts.tmpl` | `<outputDir>/library/format<ComponentName>Label.ts` |

## Confirmation

Run via Bash:
```bash
find <outputDir> -type f | sort
```

Print:
- `βœ“ Component scaffolded at <outputDir>`
- `Note: ensure react-native-reanimated is installed in your project.`

## Rules

- Never run `git add`, never commit, never create a branch. Scaffolding only.
- Never overwrite existing files β€” the collision check blocks the whole run.
- Do not install dependencies.
- Do not reformat the user's existing code.
- Do not push. Do not open a PR.
61 changes: 61 additions & 0 deletions src/commands/rn-component/references/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Layered Hook Architecture

This document defines the layering contract for React Native components scaffolded by `/rn-component`.

## Layers

### View (`index.tsx`)
- JSX only β€” no state, no logic, no side effects.
- Consumes exactly three hooks: `useStyles`, `useReanimatedStyles`, and `use<ComponentName>ViewModel`.
- Receives props typed as `I<ComponentName>Props`.

### ViewModel (`hooks/use<ComponentName>ViewModel/`)
- Single owner of all component logic.
- May use: `useState`, `useReducer`, `useMemo`, `useCallback`, `useEffect`, store subscriptions.
- Calls functions from `services/` and `library/` as needed.
- Returns `IUse<ComponentName>ViewModelReturn`.

### Static Styles (`styles.ts`)
- Exports `useStyles(props: I<ComponentName>Props)` β€” a hook that returns a `StyleSheet.create({})` result.
- Memoized with `useMemo`.
- No animated properties here.
Comment on lines +18 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: High

The architecture document states that the static styles hook should be "Memoized with useMemo". However, the corresponding template (styles.ts.tmpl) does not include useMemo:

import { StyleSheet } from 'react-native';
import type { I{{ComponentName}}Props } from './types';
export const useStyles = (props: I{{ComponentName}}Props) => {
  return StyleSheet.create({ container: { flex: 1 } });
};

This inconsistency means that scaffolded components will not have the memoization that the architecture promises, potentially causing unnecessary re-calculations on every render. A developer reading the architecture doc would expect useMemo wrapping; the absence creates a documentation–implementation gap. The mismatch is intentional per commit 23ee45d (which removed useMemo from the template), but the architecture document was not updated to match. This is a documentation bug.

Code Suggestion:

### Static Styles (`styles.ts`)
- Exports `useStyles(props: I<ComponentName>Props)` β€” a hook that returns a `StyleSheet.create({})` result.
- No animated properties here.

Evidence: path:src/commands/rn-component/references/templates/styles.ts.tmpl


### Animation Styles (`hooks/useReanimatedStyles/`)
- All `react-native-reanimated` logic lives here.
- Receives `IUseReanimatedStylesProps` (never static styles).
- Never contains `StyleSheet.create` calls.

### Services (`services/`)
- Plain async functions (API calls, AsyncStorage, etc.).
- No hooks allowed.
- Called from ViewModel only β€” never from the View.

### Library (`library/`)
- Pure utility functions.
- No hooks, no side effects.
- May be called from ViewModel or from services.

## Type conventions
- Interfaces use the `I` prefix: `IComponentNameProps`, `IUseComponentNameViewModelReturn`.
- Plain types (union, intersection, alias) have no prefix.
- Every folder that has public exports has a barrel `index.ts`.

## Barrel cycle rule
Templates use deep relative imports only (e.g. `./types`, `../useReanimatedStyles/types`).
Never use `../..` shorthand β€” it creates import cycles between barrels.

## JSX conditional rendering
Never wrap a JSX branch of a ternary in `()`. Use the line-break form:

```tsx
{isRequired
?
<Icon
icon="Asterisk"
size="small_16"
/>
: null
}
```

See `src/rules/jsx-style.md` for the full rule and rationale.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './use{{ComponentName}}ViewModel';
export * from './useReanimatedStyles';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { I{{ComponentName}}Props } from '../../types/I{{ComponentName}}Props';
import type { IUse{{ComponentName}}ViewModelReturn } from './types';

export const use{{ComponentName}}ViewModel = (
props: I{{ComponentName}}Props,
): IUse{{ComponentName}}ViewModelReturn => {
// state, effects, handlers

return {};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface IUse{{ComponentName}}ViewModelReturn {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './IUse{{ComponentName}}ViewModelReturn';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
import type { IUseReanimatedStylesProps } from './types';

// react-native-reanimated logic only β€” never static styles here.
export const useReanimatedStyles = (props: IUseReanimatedStylesProps) => {
const progress = useSharedValue(0);

const animatedContainer = useAnimatedStyle(() => ({
opacity: progress.value,
}));

return {
animatedContainer,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface IUseReanimatedStylesProps {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './IUseReanimatedStylesProps';
13 changes: 13 additions & 0 deletions src/commands/rn-component/references/templates/index.tsx.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { View } from 'react-native';
import type { I{{ComponentName}}Props } from './types';
import { useStyles } from './styles';
import { use{{ComponentName}}ViewModel, useReanimatedStyles } from './hooks';

export const {{ComponentName}}: React.FC<I{{ComponentName}}Props> = (props) => {
const styles = useStyles(props);
const reanimatedStyles = useReanimatedStyles({});
const {} = use{{ComponentName}}ViewModel(props);

return <View style={styles.container} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function format{{ComponentName}}Label(input: string): string {
return input.trim();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './format{{ComponentName}}Label';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function fetch{{ComponentName}}Data(): Promise<unknown> {
return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './fetch{{ComponentName}}Data';
10 changes: 10 additions & 0 deletions src/commands/rn-component/references/templates/styles.ts.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StyleSheet } from 'react-native';
import type { I{{ComponentName}}Props } from './types';

export const useStyles = (props: I{{ComponentName}}Props) => {
return StyleSheet.create({
container: {
flex: 1,
},
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface I{{ComponentName}}Props {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './I{{ComponentName}}Props';
43 changes: 43 additions & 0 deletions src/rules/conventional-commits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Conventional Commits

## Commit types

| Type | When to use |
|---|---|
| `feat` | New feature |
| `fix` | Bug fix |
| `docs` | Documentation only |
| `style` | Formatting, whitespace (no logic change) |
| `refactor` | Code restructuring without feature/fix |
| `perf` | Performance improvement |
| `test` | Adding or updating tests |
| `build` | Build system, dependencies |
| `ci` | CI/CD configuration |
| `chore` | Maintenance, tooling |
| `raw` | Raw data, config files |
| `cleanup` | Dead code removal, cleanup |
| `remove` | Removing features or files |

## Commit format

```
<type>(<scope>): <short description>

[optional body if subject is not enough]

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

The new conventional-commits.md rule includes a hardcoded co-author line specific to Claude Haiku 4.5. The feature skill (which references this rule) instructs its implementer agent (Sonnet) to use Co-Authored-By: Claude Sonnet 4.6 .... The commit skill also references the rule and uses Haiku. This creates a mismatch: if a non-commit skill blindly copies the exact commit format from the rule, it will produce the wrong co-author line. The rule should either omit the co-author line (since it varies per agent) or provide a placeholder like Co-Authored-By: <agent model name>. This is a maintenance risk that could lead to incorrect attribution in commits.

Code Suggestion:

## Commit format

Evidence: path:src/commands/feature/SKILL.md, path:src/skills/commit/SKILL.md

```

- Subject: max 50 characters, imperative mood ("add", not "added")
- Scope: optional, keep it short (1 word max: `auth`, `ui`, `config`)
- Description: 2–4 words only β€” no full sentences
- Body: only when the subject alone is insufficient

## Rules

- Never mix unrelated changes in one commit
- Prefer smaller, focused commits
- If a file contains changes of different natures, split them and mention in the body
- Use `git diff HEAD -- <file>` to inspect specific changes before deciding grouping
- Never use `git add .` or `git add -A` β€” always stage specific files by name
- Never run `git push` β€” committing only, pushing is the user's responsibility
5 changes: 5 additions & 0 deletions src/rules/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ Always follow this order when working with changes:
3. `/pr` β€” open PR from the branch to main

Never commit directly to `main` or `master`. If already on `main`, run `/branch` first before any commits.

## Branch naming conventions

- Release branches: `release/v{version}` β€” always include the `v` prefix (e.g. `release/v0.2.0`, `release/v1.0.0`).
- Feature branches: `feat/{description}` (e.g. `feat/atoms-components`).
Loading