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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "forge-workflow",
"displayName": "Forge",
"version": "1.0.0",
"version": "1.1.0",
"description": "Gated spec-to-ship lifecycle for Claude Code. One command determines where a project stands and does the next thing: requirements discovery to 95 percent confidence, toolchain and repository bootstrap, then test-driven implementation with enforced resumability, documentation drift gates, and tagged releases.",
"author": {
"name": "Dailen Gunter",
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,34 @@ jobs:
- name: Manifests parse, hooks references resolve, guide present
run: node .github/scripts/manifest-check.js

- name: node --check every hook script
- name: node --check every hook script and shipped template
run: |
set -e
shopt -s nullglob
found=0
for f in scripts/*.js; do
for f in scripts/*.js templates/*.js; do
found=1
echo "checking $f"
node --check "$f"
done
if [ "$found" -eq 0 ]; then
echo "no scripts found under scripts/"
echo "no scripts found under scripts/ or templates/"
exit 1
fi

- name: Typography check (ASCII only)
run: node .github/scripts/typography-check.js

# The protection tests push against disposable bare repositories in a
# temp directory, so git needs an identity and nothing leaves the runner.
- name: Configure git identity for the disposable-remote tests
run: |
git config --global user.email "ci@example.invalid"
git config --global user.name "Forge CI"

- name: Tests
run: node --test

# The Claude Code CLI is installable in CI as an npm package, so plugin
# validation runs as a real gate here. If that ever stops being true,
# replace this step with JSON Schema validation against
Expand Down
72 changes: 68 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,76 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.1.0] - 2026-08-02

Capability-based default-branch protection, so a free-tier account with a
private repository is no longer blocked at bootstrap.

Phase 2 previously required a GitHub ruleset on `main`. GitHub reserves that
for paid plans on private personal repositories and answers `Upgrade to GitHub
Pro or make this repository public to enable this feature`, which left a solo
developer on a free plan with a bootstrap gate they could only pass by paying
or by publishing a private repository. Neither is an acceptable price for a
lifecycle gate.

### Added

- `templates/branch-protection.js`: a provider-neutral protection tool.
`detect`, `apply`, `verify`, `selftest`, `gate`, `migrate`, `report`, and
`status` subcommands, with adapters for GitHub (rulesets, falling back to
classic branch protection on older Enterprise Server), GitLab (protected
branches), and an explicit fallback for self-hosted or unrecognised hosts. It
takes the strongest tier the host and account actually support and records
the provider, mechanism, and verification evidence in
`.forge/protection.json`.
- `templates/history-guard.js`: a managed `pre-push` history-integrity guard.
It reads the ref-update records git writes to a pre-push hook's stdin and
refuses deletion of the protected branch and non-fast-forward updates to it,
while allowing fast-forward pushes and initial branch creation. It fails
closed, with a message naming the fix, when it cannot see the ref records.
- `branch-protection.js selftest`, which proves the guard end to end against
disposable repositories in a temp directory. Every recursive delete goes
through a check that refuses anything that is not a directory the tool itself
created under the system temp directory with its own prefix.
- A test suite under `tests/`, run with `node --test tests/` and wired into CI.

### Changed

- Phase 2's protection step is now capability based. The gate item is
"default-branch history protection verified", satisfied by either verified
server-side enforcement or verified managed local enforcement with its
narrower trust boundary recorded. An unavailable paid hosting feature is no
longer a fatal bootstrap failure.
- `templates/lefthook.yml` runs the history check first, with `use_stdin: true`
so lefthook forwards git's ref records to it, and `piped: true` so the secret
scan, lint, build, and test commands do not run after it has already refused
the push. The command is named `00_history` because lefthook orders commands
by priority, then by the leading number in the name, then alphabetically,
never by their position in the file.
- `verify` inspects rather than installs. An earlier form called the installer,
which meant a hook the user had deleted was silently recreated and then
reported as verified. It now also confirms the hook is somewhere git will
actually run it, honouring `core.hooksPath`, and that `lefthook install` has
been run rather than trusting `lefthook.yml` alone.
- The recorded `protections` list reflects what was verified rather than being
written unconditionally, so the gate's coverage check is live.
- `forge-standards` states the protection policy once, behaviourally and
without naming a host, alongside its trust boundary.
- The always-strict repository visibility gate now covers later changes to
visibility as well as the initial choice. A hosting feature that is only
available on public repositories is never a reason to change it, and the
tool refuses to issue a visibility mutation at all.
- The hosted guide gains a "Which edition to install" section (new section 03, later
sections renumbered) explaining the two editions and which Claude generation each
targets, plus a 1.0.0 update notice and a version stamp in the title block. Readers
landing on the install section are now told which edition those commands install.
Guide only, so no version bump.
targets, plus a version stamp in the title block. Readers landing on the install
section are now told which edition those commands install.

### Migration

A project whose environment phase stalled on a paid-plan ruleset resumes with
`node .forge/branch-protection.js migrate`, which re-detects provider
capability, installs and verifies the fallback, and names exactly which
recorded blocker to clear. Unrelated blockers are preserved.

## [1.0.0] - 2026-07-27

Expand Down Expand Up @@ -82,6 +145,7 @@ Initial public release.
- Illustrated user guide hosted on GitHub Pages.
- Marketplace distribution via the `dailen` marketplace, plus a manual skills-directory install path.

[Unreleased]: https://github.com/DailenG/forge-workflow/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/DailenG/forge-workflow/compare/v1.1.0...HEAD
[1.1.0]: https://github.com/DailenG/forge-workflow/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/DailenG/forge-workflow/compare/v0.1.0...v1.0.0
[0.1.0]: https://github.com/DailenG/forge-workflow/releases/tag/v0.1.0
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,19 @@ skills/ the five skills (forge, forge-spec, forge-env, forge-code
hooks/hooks.json SessionStart, PreToolUse, PostToolUse, Stop wiring
scripts/*.js the four Node hook scripts
templates/ project-file templates forge writes into user projects
(branch-protection.js and history-guard.js are real,
tested programs, not fill-in-the-blank scaffolds)
tests/ node:test suites, run with `node --test`
docs/index.html the hosted guide (GitHub Pages, main branch /docs)
.github/ CI workflow, issue and PR templates, CI helper scripts
```

Most of this repo is prompt text, which cannot be unit tested. Two shipped
programs are the exception: `templates/branch-protection.js` and
`templates/history-guard.js` run in the user's project rather than in Claude's
context, so they have real tests under `tests/`. Changing either without
running `node --test` is how a guard silently stops guarding.

---

## Local development loop
Expand Down Expand Up @@ -111,9 +120,15 @@ What is hot vs what needs a reload:
Validate before committing anything:

```powershell
node --test
claude plugin validate --strict .
```

The disposable-remote tests create bare repositories under the system temp
directory and push to them. They never touch a real remote, and every cleanup
is refused unless the path is a directory the tool itself created with its own
prefix.

---

## Publishing changes to users
Expand Down
16 changes: 12 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ Thanks for helping improve Forge. This is a Claude Code plugin (skills plus hook

## Test a change locally before opening a pull request

1. **Validate the manifests.** From the repository root:
1. **Run the test suite.** The shipped scripts under `templates/` have real tests. No dependencies, no install step:

```
node --test
```

The disposable-remote tests create bare repositories under the system temp directory and push to them. They never touch a real remote, and every cleanup goes through a check that refuses any path that is not a directory the tool itself created with its own prefix. Git needs `user.name` and `user.email` set for them to pass.

2. **Validate the manifests.** From the repository root:

```
claude plugin validate --strict .
```

This checks `marketplace.json` and `plugin.json`, the skill frontmatter, and `hooks/hooks.json`. It must pass with no errors before you open a pull request.

2. **Load your working copy.** Point Claude Code at your checkout rather than the published marketplace so you test the exact files you edited. This repository's `marketplace.json` is named `dailen-dev` (the published catalog is the separate `dailen` marketplace), so install from `@dailen-dev`:
3. **Load your working copy.** Point Claude Code at your checkout rather than the published marketplace so you test the exact files you edited. This repository's `marketplace.json` is named `dailen-dev` (the published catalog is the separate `dailen` marketplace), so install from `@dailen-dev`:

```
claude plugin marketplace add ./
Expand All @@ -21,15 +29,15 @@ Thanks for helping improve Forge. This is a Claude Code plugin (skills plus hook

On Linux and macOS, make the hook scripts executable first: `chmod +x scripts/*.js`.

3. **Reload after hook or script changes.** Edits to `skills/**/SKILL.md` apply on the next turn, but anything under `hooks/` or `scripts/` needs a reload before it takes effect:
4. **Reload after hook or script changes.** Edits to `skills/**/SKILL.md` apply on the next turn, but anything under `hooks/` or `scripts/` needs a reload before it takes effect:

```
/reload-plugins
```

A full restart of Claude Code works too. If you only edited a `SKILL.md`, no reload is needed.

4. **Exercise the path you changed.** Run `/forge` (or the specific phase command) in a scratch project and confirm the behavior. For hook changes, confirm the SessionStart and Stop hooks still fire by watching for the injected `CONTINUE.md` state block.
5. **Exercise the path you changed.** Run `/forge` (or the specific phase command) in a scratch project and confirm the behavior. For hook changes, confirm the SessionStart and Stop hooks still fire by watching for the injected `CONTINUE.md` state block.

## Bump the version on any behavior change

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ On Linux and macOS, make the hook scripts executable: `chmod +x ~/.claude/skills
## The three phases and their gates

1. **Spec.** Requirements discovery. Claude asks questions in small batches and scores its understanding across ten areas, reporting the lowest score rather than the average. It keeps going until the lowest reaches 95. The phase ends with a written `docs/SRS.md` and a full stop: **Claude will not approve its own spec.** You read it and approve. This gate is always strict.
2. **Env.** Toolchain and repository bootstrap. It inventories the machine, installs only what is genuinely missing, sets up the repo, testing harness, git hooks, and CI, and proves the test runner actually reports failures rather than silently passing.
2. **Env.** Toolchain and repository bootstrap. It inventories the machine, installs only what is genuinely missing, sets up the repo, testing harness, git hooks, and CI, and proves the test runner actually reports failures rather than silently passing. Default-branch protection is capability based: server-side enforcement where the host and account provide it, a proven local `pre-push` history guard where they do not. That is why a free-tier account with a private repository is not blocked here, and **a private repository is never made public to satisfy a gate.**
3. **Code.** Test-driven implementation, one short-lived branch per vertical slice. Tests are written first and watched to fail before they pass. A pre-push hook runs build, tests, lint, and a secret scan, and it is the only automated gate before `main`, so `--no-verify` is prohibited. When the milestone backlog empties and the release gates pass, Forge switches to a strict mode and proposes a tagged release.

Default mode is **FLOW** (proceed between slices without asking, report after each). **STRICT** engages automatically as a release comes into reach, and some gates are always strict regardless of mode: SRS approval, repository visibility, anything needing elevation, discarding uncommitted work, tagging or publishing, adding a dependency not named in the SRS, and any discrepancy between the record and the repository.
Expand All @@ -90,7 +90,10 @@ Default mode is **FLOW** (proceed between slices without asking, report after ea
| `TODO.md` | Work needed, in progress, completed |
| `docs/SRS.md` | The specification. Living, amended by change log only |
| `docs/DECISIONS.md` | Dated decision record |
| `docs/ENVIRONMENT.md` | Machine profile, tool versions, manual steps performed |
| `docs/ENVIRONMENT.md` | Machine profile, tool versions, manual steps performed, and which default-branch protection tier is in force |
| `.forge/branch-protection.js` | Provider-neutral protection tool: detect, apply, verify, gate, migrate |
| `.forge/history-guard.js` | Managed `pre-push` guard refusing default-branch deletion and non-fast-forward pushes |
| `.forge/protection.json` | Recorded provider, tier, mechanism, trust boundary, and verification evidence |
| `docs/traceability.md` | Requirement to test mapping. v1.0.0 cannot be tagged until it is complete |
| `docs/docs-manifest.yml` | Doc page to symbol map, drives the CI drift gate |
| `docs/images/MANIFEST.md` | Screenshot inventory and capture state |
Expand Down
27 changes: 27 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,33 @@ <h4>About that "pre-push check"</h4>
force its way past it.
</p>
</div>

<div class="note">
<h4>Protecting the main line of work</h4>
<p>
The main copy of your project must not be deletable, and must not accept a save that throws
away history. Forge sets that up in whichever of two ways your hosting account actually
allows.
</p>
<p>
<strong>Best case, the host enforces it.</strong> The rule lives on the server, so it applies
to everything and everyone, including changes made through the website.
</p>
<p>
<strong>Otherwise, your machine enforces it.</strong> Some hosting plans reserve that server
setting for paid accounts. GitHub free personal accounts, for example, answer "Upgrade to
GitHub Pro or make this repository public to enable this feature" on a private project.
Forge does not buy anything and <strong>never</strong> makes a private project public to get
around it. It installs a local guard instead, which refuses a history-destroying save from
this computer, and tells you once what that does not cover: a copy of the project on another
machine that was never set up, a change made through the website, a guard someone deleted,
or somebody who has your password. Then it carries on.
</p>
<p>
Either way, Forge proves the guard works before trusting it, using throwaway practice copies
of a project rather than your real one, and writes down which of the two is in force.
</p>
</div>
</div>
</section>

Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "forge-workflow",
"version": "1.1.0",
"private": true,
"description": "Development-time test harness for the forge-workflow Claude Code plugin. Not published, and not part of the installed plugin.",
"scripts": {
"test": "node --test"
},
"license": "MIT"
}
Loading
Loading