---
alwaysApply: true
---

# AGENTS.md

This file provides guidance to LLM agents when working with code in this
repository.

## Project Overview

GitHub Action that creates/updates Git tags in repositories. Commonly used to
maintain major (`v1`) and minor (`v1.2`) version tags pointing to the latest
patch release (`v1.2.3`). The action uses itself in CI to manage its own version
tags.

**Important**: TypeScript sources in `src/` are transpiled to JavaScript in
`dist/`. Both are checked into the repository. A CI workflow verifies `dist/` is
up-to-date. Always run `npm run package` (or `npm run bundle`) after modifying
`src/` files.

**License Compliance**: After modifying NPM dependencies, check license status
and re-cache if needed via `npm run licensed:status` and
`npm run licensed:cache`.

## Development Commands

Package manager: npm

```bash
# Install dependencies
npm install

# Development workflow
npm run format:write  # Format code with Prettier (80 char lines)
npm run lint          # ESLint check
npm run test          # Run Jest tests
npm run package       # Build src/index.ts -> dist/index.js via Rollup
npm run bundle        # Alias: format + package

# Run a single test file
NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest tests/main.test.ts

# CI variants (suppress warnings)
npm run ci-test       # Run tests in CI mode

# Full pre-commit check
npm run all  # format → lint → test → coverage → update-readme → package

# Update README from action.yml
npm run update-readme

# Watch mode
npm run package:watch # Auto-rebuild on changes

# License compliance (after modifying dependencies)
npm run licensed:status  # Check license status of dependencies
npm run licensed:cache   # Re-cache licenses if needed
```

## Code Architecture

### Source Structure

- **[src/index.ts](src/index.ts)**: Entrypoint that imports and runs `main()`
- **[src/main.ts](src/main.ts)**: Core orchestration logic. Exports `run()`
  function that coordinates input parsing, tag processing, and output setting
- **[src/inputs.ts](src/inputs.ts)**: Input parsing and validation. Exports
  `getInputs()` that reads action inputs and `Inputs` interface
- **[src/derive.ts](src/derive.ts)**: Semver parsing and tag derivation:
  - `parseSemver()`: Parses version strings into components (prefix, major,
    minor, patch, prerelease, build)
  - `renderTemplate()`: Renders Handlebars templates with semver context
  - `deriveTags()`: Derives tags from a version string using a template
- **[src/tags.ts](src/tags.ts)**: Tag planning and execution logic:
  - `planTagOperations()`: Parses tags, pre-resolves refs to SHAs in parallel,
    plans create/update/skip operations
  - `executeTagOperation()`: Executes a single planned operation (create,
    update, or skip with logging)
  - Private helpers for tag creation, updates, and annotation handling
- **[action.yml](action.yml)**: GitHub Action metadata (inputs/outputs)
- **[tests/fixtures/](tests/fixtures)**: Mock implementations of @actions/core,
  @actions/github, and csv-parse for testing

### Tag Input Parsing

Uses `csv-parse/sync` to handle both CSV and newline-delimited formats. Supports
per-tag ref and annotation overrides using the format `tag:ref:annotation`:

- `tag` — Use default `ref` and `annotation` inputs
- `tag:ref` — Override ref for this tag (e.g., `v1:main` tags `v1` to `main`)
- `tag:ref:annotation` — Override both ref and annotation
- `tag::annotation` — Override annotation only (empty ref uses default)

Annotations can contain colons; everything after the second colon is treated as
the annotation text. Per-tag values override the global `ref` and `annotation`
inputs.

### Tag Derivation

The `derive_from` input allows automatic generation of tags from a semver
version string. Uses Handlebars templates with these placeholders:

- `{{prefix}}`: "v" or "V" if input had prefix, empty otherwise
- `{{major}}`, `{{minor}}`, `{{patch}}`: Version numbers
- `{{prerelease}}`, `{{build}}`: Optional semver components
- `{{version}}`: Full version without prefix

Default template `{{prefix}}{{major}},{{prefix}}{{major}}.{{minor}}` generates
major and minor tags (e.g., `v1.2.3` → `v1`, `v1.2`). Supports Handlebars
conditionals like `{{#if prerelease}}...{{/if}}`.

### Tag Update Logic

1. Parse and validate inputs ([inputs.ts](src/inputs.ts))
2. Plan all tag operations ([tags.ts](src/tags.ts):planTagOperations):
   - Parse `tag:ref:annotation` syntax and extract per-tag refs/annotations
   - Pre-resolve all unique refs to SHAs in parallel (optimization)
   - For each tag, check existence and determine operation:
     - If exists + fail mode: Fail action immediately
     - If exists + skip mode: Plan skip
     - If exists + update mode: Plan update if SHA or annotation differs
     - If doesn't exist (404): Plan create
3. Execute each planned operation ([tags.ts](src/tags.ts):executeTagOperation)
4. Set outputs with created/updated/skipped tag lists ([main.ts](src/main.ts))

### Testing Patterns

Uses Jest with ESM support. Key pattern for mocking ESM modules:

```typescript
// Declare mocks BEFORE importing tested module
jest.unstable_mockModule('@actions/core', () => core)

// Dynamic import AFTER mocks
const { run } = await import('../src/main.ts')
```

Mock fixtures live in `tests/fixtures/` (e.g., `core.ts` mocks @actions/core).

### Testing Best Practices

- Consider edge cases as well as the main success path
- Tests live in `tests/` directory, fixtures in `tests/fixtures/`
- Run tests after any refactoring to ensure coverage requirements are met
- Use `@actions/core` package for logging (not `console`) for GitHub Actions
  compatibility

## TypeScript Configuration

- Target: ES2022, NodeNext module resolution
- Strict mode enabled throughout
- Build outputs ESM to dist/index.js with external sourcemaps
- Line length: 80 chars (enforced by Prettier)

## CI/CD

`.github/workflows/ci.yml` runs:

1. **check-dist**: Verify bundled dist/ matches source
2. **lint**: ESLint check
3. **test**: Run Jest test suite
4. **release-please**: Semantic versioning releases
5. **release-tags**: Self-referential tag updates after release

## Release Process

This project uses [release-please](https://github.com/googleapis/release-please)
to automate versioning and releases based on
[Conventional Commits](https://www.conventionalcommits.org/).

### How It Works

1. **Commit with conventional format**: All commits must follow the Conventional
   Commits specification (e.g., `feat:`, `fix:`, `chore:`, `docs:`)
2. **Release PR is created**: release-please automatically opens/updates a
   "Release PR" that:
   - Proposes the next version number based on commit types
   - Updates `CHANGELOG.md` with all changes since last release
   - Updates version numbers in `package.json` and other files
3. **Review the Release PR**: Check the proposed version bump and changelog
   entries are correct
4. **Merge to release**: Merging the Release PR triggers:
   - Creation of a GitHub Release with the changelog
   - Publishing of a new Git tag
   - Execution of the `release-tags` workflow to update major/minor version tags

### Conventional Commit Format

All commit messages must follow this format:

```text
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

**Common types:**

- `feat:` - New feature (triggers minor version bump)
- `fix:` - Bug fix (triggers patch version bump)
- `docs:` - Documentation changes only
- `chore:` - Maintenance tasks, dependency updates
- `refactor:` - Code refactoring without feature/behavior changes
- `test:` - Adding or updating tests
- `ci:` - CI/CD configuration changes
- `BREAKING CHANGE:` - In footer, triggers major version bump

**Examples:**

```text
feat: add support for multiple tag formats

fix: handle 404 errors when tag doesn't exist

docs: update README with new examples

chore(deps): bump @actions/core to v1.10.0
```

## Action Interface

**Inputs:**

- `tags`: CSV/newline list, supports `tag:ref:annotation` syntax for per-tag
  overrides
- `derive_from`: Semver version string to derive tags from (e.g., "v1.2.3")
- `derive_from_template`: Handlebars template for tag derivation (default:
  `{{prefix}}{{major}},{{prefix}}{{major}}.{{minor}}`)
- `ref`: Default SHA/ref to tag (default: current commit)
- `when_exists`: update|skip|fail (default: update)
- `annotation`: Default annotation message for tags (default: lightweight/none)
- `dry_run`: Log planned operations without executing (default: false)
- `github_token`: Auth token (default: github.token)

Either `tags` or `derive_from` (or both) must be provided.

**Outputs:**

- `tags`: All created/updated tags
- `created`: Newly created tags
- `updated`: Updated tags
- `skipped`: Skipped tags (already matching or when_exists=skip)

## Code Style and Guidelines

- 80 character line length (Prettier)
- No semicolons, single quotes, no trailing commas
- Explicit function return types
- Type imports: `import type * as core from '@actions/core'`
- Error handling via try-catch with `core.setFailed()`
- 404 errors specifically caught to distinguish "tag doesn't exist" from other
  errors
- Use descriptive variable and function names
- Keep functions focused and manageable
- Document functions with JSDoc comments
- Follow DRY principles and avoid unnecessary complexity
- Maintain consistency with existing patterns and style
- Focus comments on explaining "why", not "what" (avoid basic unnecessary
  comments)
- Use TypeScript's type system for safety and clarity

## Pull Request Guidelines

When creating a pull request (PR), please ensure that:

- Keep changes focused and minimal (avoid large changes, or consider breaking
  them into separate, smaller PRs)
- Formatting checks pass
- Linting checks pass
- Unit tests pass and coverage requirements are met
- The action has been transpiled to JavaScript and the `dist` directory is
  up-to-date with the latest changes in the `src` directory
- If necessary, the `README.md` file is updated to reflect any changes in
  functionality or usage

The body of the PR should include:

- A summary of the changes
- A special note of any changes to dependencies
- A link to any relevant issues or discussions
- Any additional context that may be helpful for reviewers

## Code Review Guidelines

When performing a code review, please follow these guidelines:

- If there are changes that modify the functionality/usage of the action,
  validate that there are changes in the `README.md` file that document the new
  or modified functionality
