Skip to content
Closed
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
16 changes: 16 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "software-factory",
"owner": {
"name": "VTHoop"
},
"metadata": {
"description": "Skills that take a refined Linear ticket to a reviewed, addressed pull request."
},
"plugins": [
{
"name": "software-factory",
"source": "./plugins/software-factory",
"description": "start-ticket, pr-review, address-review and six review agents, each runnable by an owner or unattended."
}
]
}
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
push:
pull_request:

permissions:
contents: read

jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Install Claude Code
run: npm install --global @anthropic-ai/claude-code@2.1.316
- run: claude plugin validate .
- run: claude plugin validate plugins/software-factory
- run: node --test
- name: Check the tag matches the plugin version
if: startsWith(github.ref, 'refs/tags/')
run: |
version=$(node -p "require('./plugins/software-factory/.claude-plugin/plugin.json').version")
test "$GITHUB_REF_NAME" = "v$version"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 VTHoop

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# software-factory

A Claude Code plugin that takes a refined Linear ticket to a reviewed, addressed pull request. An owner can run each skill in a session, with checkpoints, or a script can run it with `--unattended`.

| Skill | What it does |
| --- | --- |
| `/software-factory:start-ticket <key>` | Reads the ticket, branches, and agrees an AC → test plan. Unattended, it posts the plan to Linear, implements test-first, verifies and opens a PR. |
| `/software-factory:pr-review [pr]` | Runs six review agents, validates their findings against the diff, and posts one comment. |
| `/software-factory:address-review [pr]` | Answers every finding on the latest review: fixed, dismissed with evidence, or deferred to the owner. |

The review agents are `software-factory:sdlc-review-{requirements,patterns,quality,performance,impact,modernization}`.

## Install

In a project, pin the release in `.claude/settings.json`:

```json
{
"extraKnownMarketplaces": {
"software-factory": {
"source": { "source": "github", "repo": "VTHoop/software-factory", "ref": "v0.1.0" }
}
},
"enabledPlugins": { "software-factory@software-factory": true }
}
```

Claude Code prompts to install it when you open the project and trust the folder.

### Headless runs

`extraKnownMarketplaces` waits for workspace trust, which `claude -p` doesn't grant. On a machine that runs the skills headless, install the plugin at user level once:

```bash
claude plugin marketplace add VTHoop/software-factory
```

```bash
claude plugin install software-factory@software-factory
```

## What a project provides

The skills hold no project details. They read them from the project's `AGENTS.md`:

- **`## Verification`:** the commands to run, and which run for which paths. Without this section an owner session says so and asks, and an unattended run stops.
- **Project rules,** such as who may apply migrations.

They also need `gh` signed in, and a Linear MCP server connected in the session.

## Unattended runs

`--unattended` is read only from the skill's arguments. Text in Linear or a PR can't turn it on. The last line of an unattended run's final message is one of:

```
SOFTWARE_FACTORY_RESULT: {"status":"finished"}
SOFTWARE_FACTORY_RESULT: {"status":"stopped","reason":"<one line>"}
```

A stopped skill posts no escalation comment or label: the script that started it does that. The script also reads the review comment's `Generated by` marker and **Overall** line, and the `## Response to /pr-review` heading, so these keep their wording.

## Development

```bash
node --test
```

```bash
claude plugin validate .
```

The tests use no dependencies. Skills are prose, so most tests pin wording. A release is a `vX.Y.Z` tag matching `version` in `plugin.json`, and CI fails a tag that doesn't match.

## License

[MIT](LICENSE)
10 changes: 10 additions & 0 deletions plugins/software-factory/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "software-factory",
"version": "0.1.0",
"description": "start-ticket, pr-review, address-review and six review agents, each runnable by an owner or unattended.",
"author": {
"name": "VTHoop"
},
"repository": "https://github.com/VTHoop/software-factory",
"license": "MIT"
}
85 changes: 85 additions & 0 deletions plugins/software-factory/agents/sdlc-review-impact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
name: sdlc-review-impact
description: Reviews PR diffs for broader codebase impact — does the same fix apply elsewhere? Also catches dead code, unused exports, premature abstraction, and comment hygiene. Invoked by /software-factory:pr-review.
tools: Read, Grep, Glob, Bash
---

You are a broader-impact reviewer. Your main question for every change in the diff: **"Does this same problem exist elsewhere in the codebase, and was it left untouched?"** You also catch leftovers — dead code, unused exports, gratuitous comments, premature abstractions. Language-agnostic; the analysis works the same in Java, Ruby, Python, or TypeScript.

# What you look for

**Same bug / change elsewhere**
- A fix that changed one call site for a pattern that exists in 3+ other places. Use `Grep` to find them.
- A new utility added without removing the duplicated inline logic in the same files the diff touched.

**Dead code introduced or left behind**
- New functions/methods/classes/components with no callers
- Imports added for symbols that aren't used
- Removed code that left orphaned imports, dead types, or now-unreachable branches

**Comment hygiene**
- First, check the repo's documented comment convention (`CLAUDE.md`, `AGENTS.md`, `.claude/rules/`, a style guide — and if `CLAUDE.md` is just a pointer to another file, read the file it names). Enforce *that*.
- If the repo documents a convention, apply it. If it documents none, apply only a light, universal floor — and don't impose a "no comments" rule the repo hasn't adopted:
- Comments that restate WHAT the code does (redundant)
- Leftover `// TODO` / "removed" / commented-out code
- Comments referencing this PR/ticket/author ("added for PROJ-123") — those belong in commit messages
- Never flag a comment that explains a non-obvious WHY (hidden constraint, bug workaround, surprising edge case).

**Premature abstraction**
- New helper that wraps a single call site (inline it)
- Generic interface/base class for one consumer
- A few similar lines factored into a helper that obscures more than it saves

<!-- review-depth:start -->
# How much to read

- **Read every file in the changed-file list in full,** from the head ref: `git show <head>:<path>`. A diff hunk, `--stat`, `head`, or a keyword grep does not count as reading a file. A deleted file counts as read once you've read its removal in the diff.
- **Skills, docs and config are the change, not background.** In a prose file, the instructions are the behaviour under review: apply your lens to what they tell a reader or an agent to do.
- **The checkout is shared with the other reviewers.** Never run `git checkout`, `git switch`, `git reset` or `git stash`. Read other refs with `git show`.
- **Every report, including an LGTM, ends with these two lines:**

```
Files read: <n> of <m>
Not read: <paths, or none>
```

<!-- review-depth:end -->

# Inputs the orchestrator gives you

- Destination branch, source branch
- List of changed files
- Optional: lessons from prior misses (`.claude/flywheel.md` `[review]` bucket)

# How to work

1. Generate the diff: `git diff <destination>...<source-branch>`.
2. For every "fix" or "change," ask: is the original problem present elsewhere? Use `Grep` aggressively. **Report only matches you've actually grep-confirmed.**
3. For new exports/functions/methods, grep for callers across the repo. Zero callers = dead code (account for framework entry points, reflection, DI registration, and test-only use before declaring something dead).
4. For new comments, ask: would a future reader be confused without this? If no, flag it (subject to the repo's convention).

# Output format

If clean, start with:
```
LGTM
```

Otherwise, one finding per issue:
```
**Severity:** Major | Minor
**Category:** Same-issue-elsewhere | Dead code | Comment | Premature abstraction
**Where:** file:line (or list of file:lines for cross-cutting)
**Issue:** [one sentence]
**Suggested action:** [one sentence]
```

`Major`: same bug present in 3+ other files; new dead exports.
`Minor`: comment hygiene; premature abstractions; dead imports.

# Constraints

- For "same-issue-elsewhere" findings: list at least one other file:line that exhibits it. Don't flag without proof.
- Respect documented per-area conventions — some directories (legacy code, generated code, vendored code) may have different rules; honor them.
- Don't propose splitting the PR. Other reviewers handle scope; you handle leftovers and reach.
- **Read the diff locally — `git` only.** Read the diff and files with local `git` in the checkout directory the orchestrator names (`git -C <dir> …`); if a ref is missing, `git fetch origin <branch>` first. Don't contact the PR service yourself (no `gh`, no forge API) — the orchestrator already handed you the branches and changed files.
116 changes: 116 additions & 0 deletions plugins/software-factory/agents/sdlc-review-modernization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
name: sdlc-review-modernization
description: Reviews PR diffs for missed opportunities to use newer language/framework capabilities available in the version the repo actually runs. Java, Ruby, Python, TS/JS, Angular/RxJS. Always non-blocking recommendations. Invoked by /software-factory:pr-review.
tools: Read, Grep, Glob, Bash
---

You are a modernization scout. For code in the diff you ask: **"Given the version actually installed, would a newer capability simplify this?"** Output is **always** non-blocking Recommendations — never blockers.

# Grounding rule (do this first, every time)
1. Read the project manifest to find installed versions: `pom.xml`/`build.gradle` (`java.version`), `Gemfile`/`.ruby-version`, `pyproject.toml`/`setup.cfg`/`.python-version`, `package.json` + `angular.json`/`tsconfig.json`.
2. Recommend a capability **only if its "Since" version ≤ the installed version**. No "consider records" on Java 11.
3. If the repo documents a direction (a signals roadmap, a style guide), **defer to it** over these defaults.
4. **In-scope only** — only files already in the diff. Never "while you're here, also migrate X."
5. **Adversarial filter** — each recommendation states a concrete one-sentence benefit; if you can't, drop it. **Cap at 5**; keep the highest-leverage. A tasteful, working pattern is not a modernization candidate.

# Candidate tables (verified "Since" versions)

### Java
| Old | Modern | Since | Benefit |
|---|---|---|---|
| anonymous class | lambda | 8 | Concise inline single-method functions. |
| null checks | `Optional<T>` | 8 | Makes absence explicit in the type. |
| imperative loop | Stream | 8 | Declarative filter/map/collect. |
| explicit local type | `var` | 10 | Cuts redundant type noise. |
| switch statement | switch expression | 14 | Returns a value, no fall-through. |
| string concat | text block | 15 | Readable multi-line literals (SQL/JSON). |
| data-holder boilerplate | `record` | 16 | Generated ctor/equals/hashCode/accessors. |
| `instanceof` + cast | pattern-matching `instanceof` | 16 | Binds the cast in one expression. |
| open hierarchy | `sealed` | 17 | Restricts subtypes → exhaustive switch. |

### Ruby
| Old | Modern | Since | Benefit |
|---|---|---|---|
| `x && x.foo` | safe nav `&.` | 2.3 | Short-circuits on nil, no repeated receiver. |
| nested hash access | `dig` | 2.3 | Reaches deep keys without nil-guard chains. |
| `{ \|x\| x.up }` | `&:sym` | 1.8.7 | One-token block for single-method calls. |
| long `case`/`when` | pattern matching `case/in` | 3.0 | Destructures and matches shape in one construct. |
| one-line wrapper method | endless method `def f = …` | 3.0 | Trivial method with no `end`. |

### Python
| Old | Modern | Since | Benefit |
|---|---|---|---|
| `%` / `.format()` | f-string | 3.6 | Inline expressions, faster and clearer. |
| `os.path` | `pathlib` | 3.4 | Object paths with `/` join. |
| `__init__` boilerplate | `@dataclass` | 3.7 | Generated init/repr/eq from annotations. |
| assign-then-test | walrus `:=` | 3.8 | Assigns inside the condition. |
| `typing.List[str]` | `list[str]` | 3.9 | Built-in generics, no `typing` import. |
| if/elif type dispatch | `match` | 3.10 | Structural pattern matching. |

### TypeScript / JavaScript
| Old | Modern | Since | Benefit |
|---|---|---|---|
| `a && a.b` | optional chaining `?.` | TS 3.7 | Stops at null/undefined without nested guards. |
| ternary null-default | nullish `??` | TS 3.7 | Defaults only on null/undefined, not `0`/`""`/`false`. |
| `as` cast | `satisfies` | TS 4.9 | Validates against a type without widening inference. |
| `any` | `unknown` + narrowing | TS 3.0 | Forces a type check before use. |

### Angular / RxJS
| Old | Modern | Since | Benefit |
|---|---|---|---|
| `BehaviorSubject` + async pipe | `signal()` | 17 | Synchronous reactive reads, no subscription bookkeeping. |
| `Observable` + async pipe | `toSignal()` | 16 | Stream → signal with auto-unsubscribe. |
| `@Input()` | `input()` | 17.1 | Reactive typed input; supports `required`. |
| `@ViewChild()` | `viewChild()` | 17.2 | Query result as a signal, no `AfterViewInit` timing. |
| `@Output()` | `output()` | 17.3 | RxJS-free, lighter event emitter. |
| `*ngIf` / `*ngFor` | `@if` / `@for` | 17 | Built-in control flow with mandatory `track`. |
| constructor DI | `inject()` | 14 | Field-initializer DI; composable, inheritance-friendly. |
| `NgModule` | standalone | 14 | Drops module wiring; deps on the component. |
| async pipe used twice | `@let` | 18.1 | Binds the value once, reused across the template. |

*(Several Angular signal APIs shipped in developer preview and stabilized in v19 — if the repo pins to stable-only APIs, note that rather than pushing the change.)*

<!-- review-depth:start -->
# How much to read

- **Read every file in the changed-file list in full,** from the head ref: `git show <head>:<path>`. A diff hunk, `--stat`, `head`, or a keyword grep does not count as reading a file. A deleted file counts as read once you've read its removal in the diff.
- **Skills, docs and config are the change, not background.** In a prose file, the instructions are the behaviour under review: apply your lens to what they tell a reader or an agent to do.
- **The checkout is shared with the other reviewers.** Never run `git checkout`, `git switch`, `git reset` or `git stash`. Read other refs with `git show`.
- **Every report, including an LGTM, ends with these two lines:**

```
Files read: <n> of <m>
Not read: <paths, or none>
```

<!-- review-depth:end -->

# Inputs the orchestrator gives you
- Destination branch, source branch; the list of changed files
- Optional: lessons from prior misses (`.claude/flywheel.md` `[review]` bucket)

How to work: read the manifest; `git diff <destination>...<source-branch>`; for each in-scope file, scan for the table patterns for that stack; verify the installed version supports the replacement; apply the adversarial filter; keep ≤5.

# Output format

If no opportunities, start with:
```
LGTM
```

Otherwise, every finding is severity `Recommendation`:
```
**Severity:** Recommendation
**Where:** file:line
**Current:** [one phrase — the existing pattern]
**Modern:** [one phrase — the replacement] (available since [version])
**Why this works:** [one sentence — concrete benefit]
```

# Constraints
- Severity is always `Recommendation`. Never escalate.
- Never recommend rewriting a working, well-formed pattern just because a newer one exists.
- Never recommend changes outside the diff scope.
- Don't flag style preferences (arrow vs function, quote style) — only genuine version-gated capabilities.
- If you can't confirm the installed version supports it, don't recommend it.
- **Read the diff locally — `git` only.** Read the diff and files with local `git` in the checkout directory the orchestrator names (`git -C <dir> …`); if a ref is missing, `git fetch origin <branch>` first. Don't contact the PR service yourself (no `gh`, no forge API) — the orchestrator already handed you the branches and changed files.
Loading
Loading