Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

# Publishes the gem to RubyGems.org via Trusted Publishing (OIDC) when a
# version tag is pushed. No API tokens are stored: GitHub Actions authenticates
# to RubyGems.org with a short-lived, scoped token.
#
# Prerequisites (one-time, on RubyGems.org):
# - A trusted publisher configured for this gem with:
# owner: devandreacarratta
# repository: password-forge-ruby-gem
# workflow: release.yml
# environment: release
# - For the very first publish (gem does not yet exist), configure a
# "pending trusted publisher" from your RubyGems.org profile first.

on:
push:
tags:
- "v*"

jobs:
push:
runs-on: ubuntu-latest

permissions:
contents: write
id-token: write

# Must match the environment configured on the RubyGems trusted publisher.
environment: release

steps:
- uses: actions/checkout@v5
with:
persist-credentials: false

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: ruby

- name: Publish to RubyGems
uses: rubygems/release-gem@v1
46 changes: 46 additions & 0 deletions .kiro/steering/product.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Product

## What this is

`password_forge` is a configurable password generator distributed as a Ruby gem.
It generates random passwords from four selectable character sets — uppercase,
lowercase, numeric and special — that can be switched on or off independently.
All sets are enabled by default, and an error is raised if every set is
disabled. Randomness comes from Ruby's `SecureRandom`.

The public API mirrors the design of an existing C# / NuGet package: a
constructor with four boolean flags plus a length, and a dedicated exception
when no character set is selected.

## Goals

1. **Learning vehicle.** This is the author's first Ruby gem. Code favours
clarity and idiomatic Ruby over cleverness, and everything is built with TDD.
2. **Kiro evangelism in the Ruby world.** Beyond the gem itself, the project
ships a full "Kiro-native" experience for Ruby gem authors: Kiro skills,
project steering (this folder), hooks, and an MCP server. The repository is
meant to be a reference example of building and shipping a gem the Kiro way.

## Audience

- Ruby developers who need a small, dependency-free password generator.
- Ruby developers curious about using Kiro to build and release gems.

## Language

All repository content (code, comments, docs, commit messages) is in **English**.
The only exception is the author's personal development diary, which is kept
locally in `private-notes/` and excluded from the repository.

## Release roadmap

The project ships in small, tagged increments. Each version is merged to `main`
before the next begins:

- **0.0.1** — Core generator, character sets, validation, tests, docs. (done)
- **0.1.0** — First public release on RubyGems.org via Trusted Publishing.
- **0.2.0** — Kiro skills for gem authors (feature TDD, version bump, release).
- **0.3.0** — Project `.kiro/` folder with full steering and conventions.
- **0.4.0** — Kiro hooks (run specs on save, changelog reminders, etc.).
- **0.5.0** — A Ruby MCP server exposing password generation as a tool.
- **0.6.0** — A fluent/builder API layered on top of the keyword-argument API.
67 changes: 67 additions & 0 deletions .kiro/steering/structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Structure

## Directory layout

```
password_forge/
├── lib/
│ ├── password_forge.rb # Entry point: requires all components
│ └── password_forge/
│ ├── version.rb # VERSION constant
│ ├── errors.rb # Error, NoCharsetSelectedError
│ ├── charset.rb # Charset value object (character sets + build)
│ ├── validation.rb # Validation module (selection + length)
│ └── generator.rb # Generator class + PasswordForge.generate
├── spec/
│ ├── spec_helper.rb
│ ├── password_forge_spec.rb # Top-level (version) spec
│ └── password_forge/
│ ├── charset_spec.rb
│ ├── validation_spec.rb
│ └── generator_spec.rb
├── sig/ # RBS type signatures
├── .github/workflows/
│ ├── main.yml # CI: RSpec matrix + RuboCop
│ └── release.yml # Trusted Publishing on v* tags
├── .kiro/ # Kiro steering / skills / hooks
├── private-notes/ # Local-only, git-ignored (dev diary)
├── password_forge.gemspec
├── Gemfile
├── Rakefile
├── README.md
├── CHANGELOG.md
└── LICENSE.txt
```

## Architecture

The internal design mirrors the original C# separation of concerns while
staying idiomatic Ruby:

- **`PasswordForge::Charset`** — a module acting as a value object. Holds the
frozen `UPPER`, `LOWER`, `NUMERIC` and `SPECIAL` constants and a `build`
method that returns the pool of characters for the selected sets.
- **`PasswordForge::Validation`** — a module with `validate_charset_selection`
(raises when no set is selected) and `validate_length` (positive integer).
- **`PasswordForge::NoCharsetSelectedError`** — the equivalent of the C#
`InvalidCharSetException`; subclass of `PasswordForge::Error`.
- **`PasswordForge::Generator`** — the public class. The constructor validates
input and builds the pool; `#generate` returns a `SecureRandom`-backed
password.
- **`PasswordForge.generate(**options)`** — a top-level convenience wrapper.

## Public API conventions

- The `Generator` constructor uses keyword arguments:
`upper_case:`, `lower_case:`, `numeric_case:`, `special_case:` (all `true`),
and `length:` (default 16). This matches the C# constructor parameter names.
- A fluent/builder API is planned for v0.6.0 and must be **additive**: the
keyword-argument API keeps working unchanged.

## Naming

- **Gem name:** `password_forge` (underscore) — what users `gem install`.
- **GitHub repo:** `password-forge-ruby-gem` (hyphens) — more descriptive for
discovery. The two intentionally differ.
- Namespace all code under the `PasswordForge` module; one file per component
under `lib/password_forge/`.
68 changes: 68 additions & 0 deletions .kiro/steering/tech.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Tech

## Stack

- **Language:** Ruby, `required_ruby_version >= 3.0.0`.
- **Test framework:** RSpec.
- **Linter:** RuboCop (config in `.rubocop.yml`, `TargetRubyVersion: 3.0`).
- **Randomness:** `SecureRandom` (standard library, no runtime dependencies).
- **Type signatures:** RBS stubs under `sig/`.

The gem has **no runtime dependencies**.

## Common commands

Run from the `password_forge/` directory:

```bash
bundle install # install development dependencies
bundle exec rake # default task: RSpec + RuboCop
bundle exec rspec # run the test suite only
bundle exec rubocop # run the linter only
bundle exec rubocop -A # auto-correct safe offences
gem build password_forge.gemspec # build the gem locally
bin/console # interactive prompt with the gem loaded
```

## Development workflow

- **TDD.** Write the spec first, watch it fail, implement to green, then
refactor. Keep the suite and RuboCop green before every commit.
- **Branches.** Do feature work on a `feature/vX.Y.Z-*` branch and merge to
`main` per milestone. Never push directly to `main` for feature work.
- **Versioning.** Semantic Versioning. Bump `lib/password_forge/version.rb`,
update `CHANGELOG.md` (Keep a Changelog format), then tag `vX.Y.Z`.

## Release process (Trusted Publishing)

Publishing is automated via OIDC — no API tokens are stored.

1. A one-time setup on RubyGems.org registers a trusted publisher:
- RubyGem name: `password_forge`
- Repository owner: `devandreacarratta`
- Repository name: `password-forge-ruby-gem`
- Workflow filename: `release.yml`
- Environment: `release`
For the very first publish (gem not yet on RubyGems), a **pending** trusted
publisher is registered from the RubyGems profile before the gem exists.
2. `.github/workflows/release.yml` triggers on `v*` tags, uses
`rubygems/release-gem@v1` with `contents: write` + `id-token: write` and the
`release` GitHub environment.
3. Releasing = bump version, update CHANGELOG, merge to `main`, push the tag.

## Key decisions (recorded so they are not re-litigated)

- **Gem email removed.** `spec.email` is intentionally omitted from the gemspec
(it is optional and would be public). The RubyGems account email is separate
and private.
- **`Gemfile.lock` is not committed.** Committing it pinned `BUNDLED WITH 4.x`,
which broke CI on Ruby < 3.2 (Bundler 4 requires Ruby 3.2+). Gems resolve the
lockfile per environment, so it is git-ignored.
- **Gem name vs repo name differ on purpose** (`password_forge` vs
`password-forge-ruby-gem`).
- **MFA required for pushes** via `rubygems_mfa_required = "true"` in the
gemspec.
- **CI splits test and lint:** RSpec runs across Ruby 3.0–3.4; RuboCop runs once
on 3.4 to avoid version-specific style noise.
- **Personal diary** lives in `private-notes/` (git-ignored), everything else is
English and tracked.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.0.1] - 2026-09-05

### Added

- `PasswordForge::Generator` with keyword-argument constructor
(`upper_case:`, `lower_case:`, `numeric_case:`, `special_case:`, `length:`)
and a `SecureRandom`-backed `#generate`.
- `PasswordForge::Charset` value object exposing the `UPPER`, `LOWER`,
`NUMERIC` and `SPECIAL` character sets and a `build` method.
- `PasswordForge::Validation` for charset selection and length checks.
- `PasswordForge::NoCharsetSelectedError`, raised when no character set is
selected.
- `PasswordForge.generate` convenience wrapper.
- RSpec test suite, RuboCop configuration and README.

[Unreleased]: https://github.com/devandreacarratta/password-forge-ruby-gem/compare/v0.0.1...HEAD
[0.0.1]: https://github.com/devandreacarratta/password-forge-ruby-gem/releases/tag/v0.0.1
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# PasswordForge

[![Ruby](https://github.com/devandreacarratta/password-forge-ruby-gem/actions/workflows/main.yml/badge.svg)](https://github.com/devandreacarratta/password-forge-ruby-gem/actions/workflows/main.yml)

A configurable password generator for Ruby with selectable character sets.

`PasswordForge` builds passwords from four character categories — uppercase,
lowercase, numeric and special — that you switch on or off independently. All
categories are enabled by default, and a clear error is raised if you disable
every one of them. Randomness is provided by Ruby's `SecureRandom`.

This gem is also a showcase for building and shipping a Ruby gem the **Kiro
way**: it ships with Kiro skills, project steering, hooks and an MCP server (see
the [Roadmap](#roadmap)).

## Installation

Install the gem and add it to the application's Gemfile by executing:

```bash
bundle add password_forge
```

If Bundler is not being used to manage dependencies, install the gem by
executing:

```bash
gem install password_forge
```

## Usage

### Quick start

```ruby
require "password_forge"

# All character sets enabled, default length of 16
PasswordForge.generate
# => "aB3$xY7!qR2@kL9%"
```

### Using the generator directly

The constructor takes four boolean flags (all `true` by default) plus a
`length`. This mirrors the design of the original C# / NuGet package:

```ruby
generator = PasswordForge::Generator.new(
upper_case: true, # include A-Z
lower_case: true, # include a-z
numeric_case: true, # include 0-9
special_case: true, # include special characters
length: 16
)

generator.generate # => "aB3$xY7!qR2@kL9%"
```

### Examples

```ruby
# A 20-character password using every character set
PasswordForge::Generator.new(length: 20).generate

# A 4-digit numeric PIN
PasswordForge::Generator.new(
upper_case: false, lower_case: false, numeric_case: true, special_case: false, length: 4
).generate
# => "8391"

# Letters only (no digits, no symbols)
PasswordForge::Generator.new(
numeric_case: false, special_case: false, length: 24
).generate
```

### Error handling

Disabling every character set raises `PasswordForge::NoCharsetSelectedError`:

```ruby
PasswordForge::Generator.new(
upper_case: false, lower_case: false, numeric_case: false, special_case: false
)
# => raises PasswordForge::NoCharsetSelectedError
```

A non-positive or non-integer `length` raises `ArgumentError`.

## Character sets

| Flag | Characters |
| -------------- | ------------------------ |
| `upper_case` | `A`–`Z` |
| `lower_case` | `a`–`z` |
| `numeric_case` | `0`–`9` |
| `special_case` | `` !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ `` |

## Roadmap

`PasswordForge` is developed in incremental, tagged releases:

- **0.0.1** — Core generator, character sets, validation, tests, docs.
- **0.1.0** — First public release on RubyGems.org via Trusted Publishing.
- **0.2.0** — Kiro skills for gem authors (feature TDD, version bump, release).
- **0.3.0** — Project `.kiro/` folder with steering and conventions.
- **0.4.0** — Kiro hooks (run specs on save, changelog reminders, and more).
- **0.5.0** — A Ruby MCP server exposing password generation as a tool.
- **0.6.0** — A fluent/builder API on top of the keyword-argument API.

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then run
`bundle exec rake` to run the tests and the linter. You can also run
`bin/console` for an interactive prompt to experiment.

To install this gem onto your local machine, run `bundle exec rake install`.

## Contributing

Bug reports and pull requests are welcome on GitHub at
<https://github.com/devandreacarratta/password-forge-ruby-gem>.

## License

The gem is available as open source under the terms of the
[MIT License](https://opensource.org/licenses/MIT).
7 changes: 4 additions & 3 deletions lib/password_forge.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# frozen_string_literal: true

require_relative "password_forge/version"
require_relative "password_forge/errors"
require_relative "password_forge/charset"
require_relative "password_forge/validation"
require_relative "password_forge/generator"

# PasswordForge generates random passwords from selectable character sets.
module PasswordForge
# Base error class for all PasswordForge-specific errors.
class Error < StandardError; end
# Implementation is added in the next version.
end
Loading