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
734be5f
feat: scaffold V1 — configs, action metadata, pure review core
aliasunder Jul 9, 2026
5f38bc3
chore: gitignore local vault symlinks
aliasunder Jul 9, 2026
fcf3c77
fix: address review — Dockerfile layer split, multi-line edge tests, …
aliasunder Jul 9, 2026
c0fe1ff
chore: add OSS community files and pre-commit tooling
aliasunder Jul 9, 2026
b0fc24f
chore: pin exact dependency versions
aliasunder Jul 9, 2026
5c6af9e
chore: switch planned OpenRouter client to official @openrouter/sdk
aliasunder Jul 9, 2026
b14ef8b
feat: add cost_summary input (default true)
aliasunder Jul 9, 2026
3402971
chore: add env-var for ambient environment access
aliasunder Jul 9, 2026
f119470
docs: drop cross-repo reference from AGENTS.md
aliasunder Jul 9, 2026
ed6a355
feat: enforce module layering boundary via eslint
aliasunder Jul 9, 2026
ea0cff3
feat: structured JSON logger with child contexts and lazy props
aliasunder Jul 9, 2026
243ea8d
docs: update logger description in AGENTS.md structure
aliasunder Jul 9, 2026
0aa3030
fix: skip npm lifecycle scripts in Docker + normalize inverted findin…
aliasunder Jul 9, 2026
23133a4
fix: allow type imports in second lint boundary pattern + injectable …
aliasunder Jul 9, 2026
70d3145
fix: address ship-check review findings — phantom diff lines, logger …
aliasunder Jul 9, 2026
ac6e817
feat: randomized prompt delimiters + digest-pinned base image with De…
aliasunder Jul 9, 2026
d53b3a8
fix: address test-audit + bug-check findings — snap bound, logger gua…
aliasunder Jul 9, 2026
2f4b7bd
style: block body for isDuplicate — multiline responses get explicit …
aliasunder Jul 9, 2026
0dfa5c8
refactor: use getBooleanInput for boolean inputs instead of hand-roll…
aliasunder Jul 9, 2026
3c7b128
docs: codify SDK-utils-first and block-body conventions in AGENTS.md
aliasunder Jul 9, 2026
db0501f
style: prettier formatting on AGENTS.md
aliasunder Jul 9, 2026
c40d0bd
feat: wire input collection in main.ts — getBooleanInput at the SDK b…
aliasunder Jul 9, 2026
de18702
chore: sweep for stale claims and unused deps after the getBooleanInp…
aliasunder Jul 10, 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
7 changes: 7 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CodeRabbit configuration.
# Docs: https://docs.coderabbit.ai/getting-started/yaml-configuration
reviews:
# Trim the recurring summary comment to just the useful parts.
poem: false
sequence_diagrams: false
review_status: false
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
dist
.git
.github
fixtures
**/__tests__
*.md
.env
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Keeps the digest-pinned base image in the Dockerfile current — pinning
# closes the mutable-tag supply-chain hole; Dependabot restores the patch
# stream the pin would otherwise lose.
version: 2
updates:
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
pull_request:
push:
branches: [main]

# Least-privilege default; no job needs more than read
permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
cache: npm

- name: Install dependencies
run: npm ci

- name: Format check
run: npm run prettier:check

- name: Lint
run: npm run lint

- name: Test
run: npm test

- name: Build
run: npm run build

# Catches Dockerfile drift that unit tests cannot (missing files, bad COPY paths)
- name: Docker build smoke
run: docker build -t umm-actually-ci .
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ dist/
.env
*.log
.DS_Store

# Symlinks into the private Obsidian vault (local dev only)
CLAUDE.local.md
TASKS.md
5 changes: 5 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Gitleaks configuration — extends the built-in default ruleset.
# Add documented, non-secret placeholders to the allowlist below if a default
# rule ever flags one (none needed today).
[extend]
useDefault = true
2 changes: 2 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
npx tsc --noEmit
npx lint-staged
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
3 changes: 3 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"semi": false
}
106 changes: 106 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# AGENTS.md

Project conventions for AI-assisted development on umm-actually.

## What this project is

A Docker-based GitHub Action that reviews pull requests with an LLM via
OpenRouter and posts one consolidated PR review with inline findings. The
review is diff-anchored but not diff-bounded: changed code is traced into
its callers, and pre-existing bugs in traced code are valid findings.

## Structure

```text
action.yml # action metadata — inputs/outputs, runs.using: docker
Dockerfile # multi-stage: build (tsc) → slim runtime
fixtures/ # test fixtures (event payloads, sample diff, LLM responses)
src/
main.ts # entrypoint — collects/validates inputs; pipeline + outputs land next PR
config.ts # action inputs → validated ActionConfig
logger.ts # structured JSON logger — levels, child contexts, lazy props
github/ # GitHub I/O: event payload → PrContext (octokit wrappers planned — next PR)
openrouter/ # (planned — next PR) OpenRouter I/O: @openrouter/sdk wrapper, structured-output ladder
diff/ # pure transforms over parse-diff output
context/ # (planned — next PR) workspace reads: conventions file, changed files, related files
review/ # pure review logic: finding schema, phases, prompt, selection, comment mapping
orchestrate.ts # (planned — next PR) the pipeline — fully testable with stub clients
```

## Module layering

Pure leaves (`diff/`, `review/`) → I/O clients (`github/`, `openrouter/`,
`context/`) → composition (`orchestrate.ts`). Only `main.ts` touches real
`process.env` and constructs SDK clients. A pure module importing an I/O
module is a backwards dependency and a bug — enforced by a
`@typescript-eslint/no-restricted-imports` block in `eslint.config.ts`
(type-only imports are allowed; they're erased at compile time).

Use the official SDKs — don't hand-roll what `@actions/core`,
`@actions/github` (octokit), `@openrouter/sdk`, or `parse-diff` already do.
Before writing any parsing or validation helper, check the SDK's utility
surface first: `@actions/core` ships `getBooleanInput` (strict YAML 1.2
booleans), which replaced a hand-rolled boolean-string Zod transform here.
Values an SDK util already parses arrive in `RawInputs` pre-parsed from the
collection boundary (main.ts) — "validation lives in config.ts" is about
where _our_ rules live, not a reason to reimplement the platform's. Zod
validates the things that are genuinely ours: LLM structured output and
action config.

Types are colocated with the code that uses them — no standalone types
files. Prefer SDK-provided types over redefining shapes.

## Code style

- Functional over OOP. Arrow functions over `function` declarations.
- Factory/closure pattern for stateful modules; single namespace export for
cohesive service surfaces (`githubClient.submitReview(…)`).
- `type` over `interface`. TypeScript strict mode. `node:` prefix for built-ins.
- Explicit return types on exports. No `any`. No `as` or `!` — use runtime
guards or schema validation to narrow.
- Immutable by default; avoid `let`. A `reduce` must return a new accumulator
each step — never mutate-and-return. When mutation is genuinely needed,
add a comment justifying it.
- Explicit names over abbreviations, everywhere — params, callbacks, locals.
- Early returns over nested `if/else`. Extract multi-clause conditionals into
named booleans. Name booleans for the affirmative state.
- Block bodies `{}` for any multiline function response — expression bodies
only for one-liners. A multi-clause boolean spanning lines gets
`{ return (...) }`; guard chains get explicit early returns, never a
chained `||`/ternary expression body.
- Named params for functions with more than two args or adjacent same-typed
args.
- Data-layer and I/O functions take `(params, logger)` — logger is required.
- `process.env` is never read via raw property access. Action inputs
(`INPUT_*`) go through `@actions/core` `getInput`/`getBooleanInput`; the
event name and payload come from `@actions/github` `context` (it reads
`GITHUB_EVENT_NAME`/`GITHUB_EVENT_PATH` and parses the JSON — don't
hand-read those); remaining ambient environment (`GITHUB_WORKSPACE`, …)
goes through the `env-var` package
(`envVar.from(env).get("NAME").required().asString()`), with the env
record injectable for tests.
- Comments explain non-obvious domain context; never restate what a
self-documenting name already says. Regex constants get doc comments.
- Relative imports use explicit `.js` extensions (ESM runtime requirement).

## Test conventions

- Tests read as a behavioral spec: one focused `it()` per behavior, named so
a failure identifies the regression without reading the body.
- `const` per test via factory helpers; `beforeEach` only when per-test
creation is genuinely impractical.
- Exact assertions over loose matchers; assert whole values over substrings
when output is deterministic.
- Two-bar rule: a test must (1) fail when the behavior breaks and (2) pass
only because the intended behavior occurred. Guard against silent no-op,
wrong-error, and early-return passes.
- Stub SDK clients are plain objects injected through factories — no HTTP
mocking libraries.
- Fixtures live in `fixtures/` and are shared across test files.

## CI conventions

- Pin third-party actions to full commit SHAs with a version comment.
- Top-level `permissions: contents: read`; jobs escalate individually.
- Secrets scoped to steps, never job-level env.
- `persist-credentials: false` on checkout unless a push is required.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

All notable changes to this project are documented in this file. It will be
updated by the release workflow (not yet built) from
[Conventional Commit](https://www.conventionalcommits.org) messages, and each
version will correspond to a GitHub Release.

## [Unreleased]

Initial development — no releases yet.
62 changes: 62 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of background
or identity.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community

Examples of unacceptable behavior include:

- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by [opening a GitHub issue](https://github.com/aliasunder/umm-actually/issues/new?labels=conduct)
with the label **conduct**. If you need to report privately, you can use
[GitHub's private vulnerability reporting](https://github.com/aliasunder/umm-actually/security/advisories/new)
to reach the maintainer confidentially.

All complaints will be reviewed and investigated promptly and fairly.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
74 changes: 74 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Contributing

Thanks for your interest in umm-actually! It's a single-purpose GitHub Action,
so the contribution loop is simple.

## What this is

A Docker-based GitHub Action that reviews pull requests with an LLM via
[OpenRouter](https://openrouter.ai) and posts one consolidated PR review with
inline findings. TypeScript inside, Node 24, strict mode throughout.

## Prerequisites

- Node.js 24 (an `.nvmrc` is provided — `nvm use`)
- Docker (for the image build check)

## Build & test

```bash
npm ci
npm test # vitest — the full unit suite
npm run lint # eslint (typescript-eslint strict)
npm run prettier:check
npm run build # tsc
docker build . # what CI's smoke step runs
```

All of these must pass before pushing — CI runs the same steps.

## Code conventions

Conventions live in [AGENTS.md](./AGENTS.md) — the single source of truth. Key
points:

- Functional style, immutable by default; no `any`, no `as`, no `!` — runtime
guards and Zod narrowing instead
- Pure modules (`diff/`, `review/`) never import I/O modules; only `main.ts`
touches `process.env` and constructs SDK clients
- Use the official SDKs (`@actions/core`, `@actions/github`,
`@openrouter/sdk`, `parse-diff`) — don't hand-roll what they already do
- Relative imports carry explicit `.js` extensions (ESM runtime requirement)
- Tests are behavioral specs: one focused `it()` per behavior, exact
assertions, `const` per test

## Pull request process

1. **Branch from `main`** with a descriptive prefix (`feat/`, `fix/`, `docs/`,
`ci/`, `chore/`)
2. **Keep PRs focused** — one logical change per PR
3. **Run the full local check** (test, lint, format, build, docker build)
before pushing
4. **Use a Conventional Commit PR title** (`feat: …`, `fix: …`) — PRs are
squash-merged and the title becomes the changelog entry
5. **Required checks must pass** — the CI workflow runs format, lint, tests,
build, and a Docker build smoke step

## Issues

- **Bug reports** and **feature requests**: open a GitHub issue
- **Security issues**: see [SECURITY.md](./SECURITY.md) — report privately,
not as a public issue

## Releases

Releases are cut by the maintainer. Each release will publish the action's
Docker image and update [`CHANGELOG.md`](./CHANGELOG.md) from Conventional
Commit messages via a release workflow (not yet built) — which is why PR
titles matter already: on a squash merge the title becomes the changelog
entry.

## License

[MIT](./LICENSE). By contributing, you agree your contributions are licensed
under the MIT License.
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Base image digest-pinned (same idiom as SHA-pinned actions in ci.yml) — the
# image builds on the consumer's runner, so a mutated tag would flow straight
# into the container that holds github_token and openrouter_api_key.
# Dependabot (docker ecosystem) keeps the digest current.

# Production dependencies — cached independently of source changes
FROM node:24-slim@sha256:cb4e8f7c443347358b7875e717c29e27bf9befc8f5a26cf18af3c3dec80e58c5 AS prod-deps
WORKDIR /app
COPY package.json package-lock.json ./
# --ignore-scripts: the prepare script runs husky, a devDependency absent here
RUN npm ci --omit=dev --ignore-scripts

# Build stage: dev dependencies + TypeScript compile
FROM node:24-slim@sha256:cb4e8f7c443347358b7875e717c29e27bf9befc8f5a26cf18af3c3dec80e58c5 AS build
WORKDIR /app
COPY package.json package-lock.json ./
# --ignore-scripts: skip husky's git-hook install — no .git in the image
RUN npm ci --ignore-scripts
COPY tsconfig.json ./
COPY src ./src
RUN npx tsc

# Runtime stage: dist + production node_modules, nothing else
FROM node:24-slim@sha256:cb4e8f7c443347358b7875e717c29e27bf9befc8f5a26cf18af3c3dec80e58c5
WORKDIR /app
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./

ENTRYPOINT ["node", "/app/dist/src/main.js"]
Comment thread
aliasunder marked this conversation as resolved.
Loading