forja and Specgate#
Specgate keeps a project's requirements, their scenarios
and the tests that prove them in step. forja can generate a project ready for it, and forja add usecase links each new use case to its requirement. This page covers what forja does; the Specgate
documentation covers Specgate itself.
Turn it on#
Specgate comes with the specgate extra. It is on by default for Java backends. On Node, ask for it:
forja init acme --yes --extras lint,specgate
forja init shop --yes --backend spring # lint, docker and specgate by defaultIn the wizard, tick "Specgate (spec-driven development)" under Extras.
What forja installs#
| What | Where |
|---|---|
@rsaglobaltech/specgate ^0.19.1 | root package.json, devDependencies |
spec, spec:status, spec:check scripts | root package.json, running specgate, specgate status and specgate check |
| The brief to fill in | docs/brief.md |
| The day-to-day guide | docs/specgate.md |
docs/brief.md starts with four fields for Specgate, then a space for the client's brief:
---
product: acme
surfaces: [api, web]
actors:
- { name: Customer, surfaces: [api, web] }
modules: []
---Paste the brief below that block as you received it. Specgate checks that every value in a scenario
comes from this text or from assumptions.md.
Adoption after the install#
When forja installs the dependencies, it adopts Specgate in the new project:
npx specgate adopt --no-capabilities
npx specgate config set test_cmd "npm test"
npx specgate config set arch_cmd "npm run arch"The third line puts the architecture in the gate: specgate check, specgate done and the harness
run npm run arch between the specification and the tests, so code in the wrong layer fails the gate
even when every test passes. On Node, npm run arch checks the core's import rules; on Java it runs
the ArchUnit test alone. It needs Specgate 0.20 or newer; an older one leaves the gate as it was.
With git on, it commits the result as chore: adopt Specgate. If you used --skip-install, or the
adoption failed (no network, for example), forja prints those commands among the next steps
instead. Either way, the next steps end with:
npx specgate draft # after you fill in docs/brief.md — see docs/specgate.md
npx specgate # where the specification stands, and what to run nextforja doctor warns until the project is adopted, which it detects by the presence of spec.md
and docs/specs/traceability.md.
Module by module#
The flow below is the one in the generated docs/specgate.md. Run npx specgate on its own whenever
you lose track: it shows where the specification stands and the command to run next.
Write the brief. Paste the client's brief into
docs/brief.mdand fill in the four fields.npx specgate draftexplains each field;npx specgate draft --agent "claude -p"lets your agent propose them.Draft a module. Your agent writes the requirements and scenarios, and Specgate checks them against the brief:
bash npx specgate draft orders --agent "claude -p"Without an agent,
npx specgate draft ordersprints the line to paste into one. Repeat until it says Ready to review.Review it with whoever knows the business. The open questions go to the client.
Archive it. The requirements join the specification:
bash npx specgate change archive draft-ordersBuild each requirement, test first. A test that names
REQ-007is the link.forja add usecasewrites the use case and that test for you; see the next section.Close the requirement. Specgate refuses while the suite is red:
bash npx specgate done REQ-007Before every pull request, run the gate:
bash npx specgate check
Each module of the specification is a hexagonal module of the code: application/<module>/ in
packages/core on Node, apps/api/.../<module>/ on Java.
forja add usecase with Specgate#
In a project where Specgate is adopted, forja add usecase also creates the requirement:
forja add usecase orders/PlaceOrderforja checks first that no file would be overwritten, so a refused command leaves no orphan requirement.
It runs
specgate new "Place order". The title comes from the class name, or from--title.It writes the use case and its test, with the new id in the test's name:
PlaceOrderUseCase.test.ts describe("REQ-003 Place order", () => { it("runs", async () => { /* ... */ }); it.todo("proves the scenario in <the feature file Specgate wrote>"); });On Java, the id goes in
@DisplayName("REQ-003 Place order"), and the pending scenario is a@Disabledtest. The use case's comment saysSpecified by REQ-003.It runs
specgate matrixto regenerate the traceability matrix, sospecgate checksees the link.
The output names the requirement, the scenario file when Specgate reports one, and the next step:
◇ REQ-003 · Place order
│ scenario to write: <the feature file>
└ Next: write the scenario, make the test prove it (npm test), then npx specgate done REQ-003The options#
| Option | Effect |
|---|---|
--req REQ-007 | names an existing requirement instead of creating one. The id must look like REQ- followed by digits. |
--title "Place an order" | the title specgate new gets, and the one in the test name |
--no-spec | no requirement and no id in the test |
--dry-run | shows the files; creates no requirement |
--req works for requirements that came out of specgate draft: the archived module already has
REQ-007, and you want the code for it.
forja add usecase orders/PlaceOrder --req REQ-007If Specgate cannot write the requirement, forja still writes the files and warns
No requirement linked: with the reason.
In CI#
With the ci extra too, the generated GitHub Actions workflow has a spec job that runs
npx -y @rsaglobaltech/specgate@0.19.1 validate . --strict. The tests themselves run in the other jobs.