@luizsantiago/spec-guardrails 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -95
- package/index.js +40 -1
- package/lib/classify-change.js +18 -0
- package/lib/constants.js +2 -0
- package/lib/gates.js +2 -0
- package/package.json +2 -2
- package/scripts/req_context.py +145 -0
- package/scripts/validate_req_analysis.py +191 -0
- package/skills/agent-architecture.md +1 -1
- package/skills/references/elicitation.md +13 -1
package/README.md
CHANGED
|
@@ -5,170 +5,254 @@
|
|
|
5
5
|
|
|
6
6
|
**Governed spec-driven development for AI coding agents.**
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **4.0.x**
|
|
8
|
+
npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **4.1.x** · Works with Cursor, Claude Code, GitHub Copilot, and Codex.
|
|
11
9
|
|
|
12
10
|
---
|
|
13
11
|
|
|
14
|
-
## What
|
|
15
|
-
|
|
16
|
-
Spec Guardrails is **not** an application framework, a vector database, or a replacement for your stack. It is an **operating model** for AI-assisted engineering:
|
|
17
|
-
|
|
18
|
-
| Layer | Role |
|
|
19
|
-
| --- | --- |
|
|
20
|
-
| **Phase guides** | Instructions the agent loads one step at a time |
|
|
21
|
-
| **Project memory** | `.specs/` — specs, tasks, validation, and state that survive chat sessions |
|
|
22
|
-
| **Structural checks** | Optional Python gates that enforce document shape and evidence hooks |
|
|
12
|
+
## What this solves
|
|
23
13
|
|
|
24
|
-
|
|
14
|
+
AI agents are fast but forgetful. Ask for a feature and you usually get code with no written requirement, no record of the decisions, no proof it works, and nothing left behind when the chat window closes. The next session starts from zero.
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
Spec Guardrails installs a **repeatable process** into your repository so the agent always works the same way:
|
|
27
17
|
|
|
28
|
-
|
|
18
|
+
1. **Write down what is being built** — before any code exists, and you approve it.
|
|
19
|
+
2. **Break it into jobs** — a checklist you can read and approve.
|
|
20
|
+
3. **Implement in small waves** — one job at a time, each committed.
|
|
21
|
+
4. **Prove it works** — a separate verification pass that did not write the code.
|
|
22
|
+
5. **Keep the record** — spec, decisions, and proof stay in your repo, in git.
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
You stay the decision-maker. The agent stops and asks instead of guessing. When the session ends, the reasoning is still there — for you, for your team, and for the next agent run.
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
| --- | --- |
|
|
34
|
-
| **Traceability** | Requirements, jobs, and verification live in version-controlled artifacts |
|
|
35
|
-
| **Controlled autonomy** | Agents propose and execute; humans approve scope, design forks, and git tiers |
|
|
36
|
-
| **Progressive depth** | Quick fixes skip ceremony; complex work gets discuss, design, and task graphs |
|
|
37
|
-
| **Platform-agnostic** | Cursor, Claude Code, GitHub Copilot, Codex, and other agents via root `AGENTS.md` |
|
|
38
|
-
| **Token efficiency** | One phase guide per turn instead of dumping the entire playbook |
|
|
39
|
-
| **Optional enforcement** | Process mode (Node only) or Brakes mode (Node + Python gates) |
|
|
26
|
+
It is **not** an application framework, a runtime, or a dependency in your build. It is a set of markdown instructions plus optional check scripts. Remove the folders and your project is untouched.
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
Deeper: [Overview](docs/guide/Overview.md) · [How it works](docs/guide/How-it-works.md) · [Concepts](docs/guide/concepts.md)
|
|
42
29
|
|
|
43
30
|
---
|
|
44
31
|
|
|
45
32
|
## Install
|
|
46
33
|
|
|
47
|
-
|
|
34
|
+
Run once in your project root:
|
|
48
35
|
|
|
49
36
|
```bash
|
|
50
37
|
npx @luizsantiago/spec-guardrails install
|
|
38
|
+
npx @luizsantiago/spec-guardrails doctor
|
|
51
39
|
```
|
|
52
40
|
|
|
53
|
-
Re-run after upgrading
|
|
41
|
+
`install` writes the agent instructions and a `.specs/` folder. Re-run it after upgrading — your existing notes are preserved. `doctor` confirms everything is wired up.
|
|
54
42
|
|
|
55
43
|
| Requirement | Role |
|
|
56
44
|
| --- | --- |
|
|
57
|
-
| **Node.js 18+** | Required —
|
|
58
|
-
| **Python 3.10+** | Optional —
|
|
45
|
+
| **Node.js 18+** | Required — installer and CLI |
|
|
46
|
+
| **Python 3.10+** | Optional — turns on the automatic checks ("Brakes mode") |
|
|
47
|
+
|
|
48
|
+
Without Python you still get the full process; the agent performs the same checks by reading the guides instead of running scripts.
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
Setup per platform: [Platform parity](docs/guide/Platform-parity.md) · First session: [Quick start](docs/guide/Quick-start.md)
|
|
61
51
|
|
|
62
52
|
---
|
|
63
53
|
|
|
64
|
-
## How
|
|
54
|
+
## How a feature flows
|
|
55
|
+
|
|
56
|
+
You work in **agent chat** — normal conversation, in your own language. You do not memorize commands or run the CLI yourself; the agent knows the process and drives it, pausing at the two points where your approval is required.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
YOU describe the work
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
┌──────────────────────┐
|
|
63
|
+
│ 1. UNDERSTAND │ Vague request? The agent asks questions
|
|
64
|
+
│ │ first and writes a requirements brief.
|
|
65
|
+
└──────────┬───────────┘
|
|
66
|
+
▼
|
|
67
|
+
┌──────────────────────┐
|
|
68
|
+
│ 2. SPECIFY │ What gets built, what "done" means,
|
|
69
|
+
│ │ what is out of scope.
|
|
70
|
+
└──────────┬───────────┘
|
|
71
|
+
│
|
|
72
|
+
◆ YOU APPROVE ◆ Nothing is coded before this.
|
|
73
|
+
│
|
|
74
|
+
▼
|
|
75
|
+
┌──────────────────────┐
|
|
76
|
+
│ 3. PLAN │ The work becomes an ordered job list.
|
|
77
|
+
│ │ Big changes also get a design step.
|
|
78
|
+
└──────────┬───────────┘
|
|
79
|
+
│
|
|
80
|
+
◆ YOU APPROVE ◆ Scope is locked here.
|
|
81
|
+
│
|
|
82
|
+
▼
|
|
83
|
+
┌──────────────────────┐
|
|
84
|
+
│ 4. BUILD │ One job at a time, tests first,
|
|
85
|
+
│ ↺ loop │ one commit each. Repeats until done.
|
|
86
|
+
└──────────┬───────────┘
|
|
87
|
+
▼
|
|
88
|
+
┌──────────────────────┐
|
|
89
|
+
│ 5. VERIFY │ A fresh pass that did not write the
|
|
90
|
+
│ │ code checks it against the spec.
|
|
91
|
+
└──────────┬───────────┘
|
|
92
|
+
▼
|
|
93
|
+
┌──────────────────────┐
|
|
94
|
+
│ 6. ARCHIVE │ Outcome and lessons folded into
|
|
95
|
+
│ │ project memory for future runs.
|
|
96
|
+
└──────────────────────┘
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Between steps the agent runs structural checks. **A failed check stops the process** rather than letting a half-written spec reach implementation.
|
|
100
|
+
|
|
101
|
+
Details: [How it works](docs/guide/How-it-works.md) · Phase-by-phase reference: [Agent commands](docs/guide/agent-commands.md)
|
|
102
|
+
|
|
103
|
+
---
|
|
65
104
|
|
|
66
|
-
|
|
105
|
+
## It sizes the work before starting
|
|
67
106
|
|
|
68
|
-
|
|
107
|
+
The full flow would be absurd for a typo. Spec Guardrails first judges **how big the change actually is**, then loads only the steps and instructions that change needs. A one-line fix skips straight to implementation; a new integration gets the design conversation.
|
|
69
108
|
|
|
70
|
-
|
|
|
109
|
+
| If the change is… | The agent runs |
|
|
71
110
|
| --- | --- |
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
|
|
111
|
+
| A tiny fix — up to 3 files, no decisions | Describe → build → verify → commit |
|
|
112
|
+
| Localized — a few files, no new patterns | Specify → build → verify |
|
|
113
|
+
| A normal feature — under 10 jobs | Specify → plan → build → verify |
|
|
114
|
+
| New architecture, an API, or infrastructure | Specify → discuss options → design → plan → build → verify |
|
|
115
|
+
| Splittable across parallel agents | The above, plus a job dependency map |
|
|
116
|
+
|
|
117
|
+
Two things follow from this, and both matter in practice:
|
|
79
118
|
|
|
80
|
-
**
|
|
119
|
+
- **Speed** — small work stays small. No ceremony is added to a change that does not need it.
|
|
120
|
+
- **Cost and accuracy** — the agent reads one short guide per step instead of the whole playbook. Less context per turn means lower token spend and fewer instructions competing for attention.
|
|
81
121
|
|
|
82
|
-
|
|
122
|
+
The agent proposes the depth and explains why; you can always ask for more or less.
|
|
83
123
|
|
|
84
|
-
|
|
124
|
+
Deeper: [Overview → Three ways to start](docs/guide/Overview.md#three-ways-to-start-pick-one) · [Loop patterns](docs/guide/loop-patterns.md)
|
|
85
125
|
|
|
86
|
-
|
|
126
|
+
---
|
|
87
127
|
|
|
88
|
-
|
|
128
|
+
## What you get, layer by layer
|
|
89
129
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
130
|
+
Each layer is useful on its own. You do not have to understand all of them to benefit from the first one.
|
|
131
|
+
|
|
132
|
+
### Artifacts — the paper trail
|
|
133
|
+
|
|
134
|
+
Every feature gets a folder of plain markdown in your repo: what was requested, what was decided, the job list, and the verification report. Because it is markdown in git, it reviews like code, diffs like code, and travels with the branch. Months later you can answer "why is this built this way?" without asking anyone.
|
|
135
|
+
|
|
136
|
+
→ [Artifacts and `.specs/`](docs/guide/Architecture.md)
|
|
137
|
+
|
|
138
|
+
### Skills — the agent's instruction manual
|
|
139
|
+
|
|
140
|
+
The process itself is written as short guides the agent reads on demand — one per step. This is why the agent behaves consistently across sessions and across tools: the instructions live in your repo, not in a prompt someone typed once. It also keeps each turn focused, since only the relevant guide is loaded.
|
|
141
|
+
|
|
142
|
+
→ [Skills and hub](docs/guide/skills-and-hub.md)
|
|
143
|
+
|
|
144
|
+
### Gates — the checks that stop bad work early
|
|
145
|
+
|
|
146
|
+
Small scripts verify structure at each handoff: does the spec state acceptance criteria, are the jobs atomic, does the commit reference a real task, is there evidence for the claim of "done". They check **shape, not opinion** — so they are predictable, and they run before you spend time reviewing. A failing gate blocks progress.
|
|
147
|
+
|
|
148
|
+
→ [Gates](docs/guide/gates.md) · [Gates and guarantees](docs/guide/Gates-and-guarantees.md)
|
|
149
|
+
|
|
150
|
+
### Requirements analysis — for when the request is still fuzzy
|
|
151
|
+
|
|
152
|
+
"Make the dashboard better" is not buildable. This layer runs a short structured interview, records the answers and the open questions, and asks you to sign off before anything is specified. It is optional and never forced — but it is where most rework is prevented, because misunderstandings surface while they are still cheap.
|
|
99
153
|
|
|
100
|
-
|
|
154
|
+
→ [Requirements analysis](docs/guide/agent-commands.md)
|
|
155
|
+
|
|
156
|
+
### Loops — how implementation actually happens
|
|
157
|
+
|
|
158
|
+
Work advances in waves rather than one giant edit: pick the next job, write the test, make it pass, commit, repeat. Each wave is small enough to review and to roll back. If a wave reveals the plan was wrong, the process stops and returns to planning instead of improvising forward.
|
|
159
|
+
|
|
160
|
+
→ [Loop patterns](docs/guide/loop-patterns.md)
|
|
161
|
+
|
|
162
|
+
### Memory — what survives the chat window
|
|
163
|
+
|
|
164
|
+
State, decisions, and lessons are written to disk as work happens. A new session — or a different agent, or a teammate — reads the current state and continues instead of restarting. Archived features become long-lived project knowledge, and repeated mistakes get recorded as lessons so they stop repeating.
|
|
165
|
+
|
|
166
|
+
→ [Memory](docs/guide/Memory.md) · [Brownfield context](docs/guide/brownfield-context.md)
|
|
101
167
|
|
|
102
168
|
---
|
|
103
169
|
|
|
104
170
|
## What lands in your repository
|
|
105
171
|
|
|
106
|
-
| Path |
|
|
172
|
+
| Path | What it is |
|
|
107
173
|
| --- | --- |
|
|
108
|
-
| `.cursor/skills/` (+ Claude, Copilot, Codex trees) |
|
|
109
|
-
| `.specs/STATE.md` |
|
|
110
|
-
| `.specs/features/NNN-slug/` | Spec,
|
|
111
|
-
| `.specs/guardrails/scripts/` |
|
|
112
|
-
| `.specs/config.yaml` | Optional project rules and
|
|
174
|
+
| `.cursor/skills/` (+ Claude, Copilot, Codex trees) | The step-by-step guides the agent reads |
|
|
175
|
+
| `.specs/STATE.md` | Current feature and next step |
|
|
176
|
+
| `.specs/features/NNN-slug/` | Spec, job list, and verification per feature |
|
|
177
|
+
| `.specs/guardrails/scripts/` | The check scripts (Brakes mode) |
|
|
178
|
+
| `.specs/config.yaml` | Optional project rules and limits |
|
|
113
179
|
|
|
114
|
-
|
|
180
|
+
All plain text, all in git. Nothing is added to your dependencies or your build output.
|
|
115
181
|
|
|
116
182
|
---
|
|
117
183
|
|
|
118
|
-
##
|
|
184
|
+
## Optional extras
|
|
119
185
|
|
|
120
|
-
Start with the
|
|
186
|
+
Start with the core flow. Turn these on when the work calls for them.
|
|
121
187
|
|
|
122
|
-
|
|
|
123
|
-
| --- | --- |
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
188
|
+
| Capability | What it adds |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| Memory search | Find past specs, decisions, and verifications by keyword |
|
|
191
|
+
| Code index | A lightweight map of an existing codebase for brownfield work |
|
|
192
|
+
| Solution exploration | Compare implementation options side by side before committing |
|
|
193
|
+
| Execution policy | Limits on which paths the agent may touch, and budgets |
|
|
194
|
+
| Episodic memory | Session notes distilled into lessons for later runs |
|
|
195
|
+
| Semantic retrieval | Search memory by meaning instead of keyword — off by default |
|
|
130
196
|
|
|
131
|
-
|
|
197
|
+
### Cursor hooks — optional, and off by default in this repo
|
|
132
198
|
|
|
133
|
-
|
|
199
|
+
On **Cursor**, `install` can register IDE hooks that check scope before a file edit and screen shell commands before they run. They are a safety net, not part of the core flow — everything above works without them.
|
|
134
200
|
|
|
135
|
-
|
|
201
|
+
Be aware they spawn a short-lived **Node** process per action, which can make the IDE feel heavy on busy sessions, especially on Windows. To reduce or remove that cost, delete the `beforeShellExecution` entry from `.cursor/hooks.json` first, then `preToolUse` if needed.
|
|
136
202
|
|
|
137
|
-
|
|
203
|
+
→ [Cursor hooks and sandbox](docs/guide/Cursor-hooks-and-sandbox.md) — what they do, symptoms, tuning, full disable
|
|
138
204
|
|
|
139
|
-
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Documentation
|
|
140
208
|
|
|
141
|
-
|
|
209
|
+
| If you want to… | Read |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| Understand the idea | [Overview](docs/guide/Overview.md) · [Concepts](docs/guide/concepts.md) |
|
|
212
|
+
| Run your first feature | [Quick start](docs/guide/Quick-start.md) |
|
|
213
|
+
| See each step in detail | [How it works](docs/guide/How-it-works.md) · [Agent commands](docs/guide/agent-commands.md) |
|
|
214
|
+
| Know what is enforced | [Gates](docs/guide/gates.md) · [Guarantees matrix](docs/guide/Guarantees-matrix.md) |
|
|
215
|
+
| Work in an existing codebase | [Brownfield context](docs/guide/brownfield-context.md) · [Memory](docs/guide/Memory.md) |
|
|
216
|
+
| Tune Cursor IDE behavior | [Cursor hooks and sandbox](docs/guide/Cursor-hooks-and-sandbox.md) |
|
|
217
|
+
| Upgrade or check stability | [CHANGELOG](docs/CHANGELOG.md) · [Stability policy](docs/guide/Stability-policy.md) |
|
|
218
|
+
| Ask something specific | [FAQ](docs/guide/FAQ.md) |
|
|
142
219
|
|
|
143
|
-
|
|
220
|
+
Full index: [docs/guide/README.md](docs/guide/README.md)
|
|
144
221
|
|
|
145
|
-
|
|
146
|
-
npx @luizsantiago/spec-guardrails@latest install
|
|
147
|
-
npx @luizsantiago/spec-guardrails doctor
|
|
148
|
-
```
|
|
222
|
+
---
|
|
149
223
|
|
|
150
|
-
|
|
224
|
+
## Contributing
|
|
151
225
|
|
|
152
|
-
|
|
226
|
+
Focused improvements to skills, gates, CLI, docs, and tests are welcome.
|
|
153
227
|
|
|
154
228
|
```bash
|
|
155
229
|
git clone https://github.com/luizssantiago92/spec-guardrails.git
|
|
156
230
|
cd spec-guardrails
|
|
157
231
|
npm install
|
|
158
232
|
npm run guardrails -- install
|
|
159
|
-
npm
|
|
233
|
+
npm test
|
|
160
234
|
```
|
|
161
235
|
|
|
162
|
-
|
|
236
|
+
Source lives in `skills/` (agent guides), `scripts/` (check scripts), `lib/` (CLI), and `test/`. Re-run `npm run guardrails -- install` after changing shipped assets, and run `npm test` before opening a PR.
|
|
163
237
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
238
|
+
We build Spec Guardrails using Spec Guardrails — the recommended contribution workflow, gate stability rules, and the adversarial test policy are in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Credits
|
|
243
|
+
|
|
244
|
+
Spec Guardrails adapts patterns from these projects. Listed here are the ones whose ideas are **actually shipped** in the package:
|
|
245
|
+
|
|
246
|
+
| Project | License | What we adapted |
|
|
247
|
+
| --- | --- | --- |
|
|
248
|
+
| [tlc-spec-driven](https://github.com/tech-leads-club/agent-skills) | CC-BY-4.0 | The specify → plan → build → verify phase model, the `.specs/` layout, and the "gates as brakes" philosophy |
|
|
249
|
+
| [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | MIT | Discuss-phase patterns (option A/B/C decision records) and definition-of-done framing |
|
|
250
|
+
| [graph-engineering](https://github.com/codejunkie99/graph-engineering) | MIT | Job dependency rules — one file owner per wave, stop rule, fake-edge detection |
|
|
251
|
+
| [loop-engineering](https://github.com/cobusgreyling/loop-engineering) | MIT | The implementation wave model behind the build loop |
|
|
252
|
+
|
|
253
|
+
Everything else — the Node CLI, the Python gates, the platform adapters, and the requirements analysis phase — is original work in this repository.
|
|
170
254
|
|
|
171
|
-
|
|
255
|
+
Full lineage, including work we cite but do not bundle: [docs/guide/credits.md](docs/guide/credits.md)
|
|
172
256
|
|
|
173
257
|
---
|
|
174
258
|
|
package/index.js
CHANGED
|
@@ -87,6 +87,11 @@ Commands:
|
|
|
87
87
|
req-analysis discover List local kickoff sources (prd.md, kickoff.md, …)
|
|
88
88
|
req-analysis promote Print next steps after brief approval
|
|
89
89
|
[--scope project|feature] Match the brief scope
|
|
90
|
+
req-analysis validate [brief.md] Gate: approved requirements brief before /specify
|
|
91
|
+
req-analysis context Assemble kickoff + brief context for Specify
|
|
92
|
+
[--scope project|feature] Scope (default: project)
|
|
93
|
+
[--slug <feature-slug>] Feature slug when scope=feature
|
|
94
|
+
[--json] Machine-readable output
|
|
90
95
|
archive-feature [feature] Fold verified feature into ROADMAP + domain spec; reset STATE
|
|
91
96
|
[--domain <slug>] Domain folder under .specs/domains/ (default: feature slug)
|
|
92
97
|
[--skip-verify] Skip validate-state (tests / recovery only)
|
|
@@ -168,6 +173,7 @@ Commands:
|
|
|
168
173
|
[--json] Machine-readable plan for agents
|
|
169
174
|
validate-traceability [feature] REQ → tasks → validation coverage chain
|
|
170
175
|
validate-quick [quick-folder] Quick-mode TASK.md / SUMMARY.md structural gate
|
|
176
|
+
validate-req-analysis [brief.md] Requirements brief gate before /specify (/elicit)
|
|
171
177
|
validate-state [feature] Completion gate before declaring a feature done
|
|
172
178
|
check-commit --message "<msg>" Conventional Commits gate
|
|
173
179
|
lessons <add|list|penalize|prune|promote|graduate|status> Lessons engine
|
|
@@ -953,8 +959,41 @@ if (command === "--version" || command === "-v" || command === "version") {
|
|
|
953
959
|
}
|
|
954
960
|
}
|
|
955
961
|
console.log(formatPromoteMessage({ scope, description }));
|
|
962
|
+
} else if (sub === "validate") {
|
|
963
|
+
const briefPath = rest.find((arg) => !arg.startsWith("--"));
|
|
964
|
+
const code = await runGate("validate-req-analysis", briefPath ? [briefPath] : []);
|
|
965
|
+
process.exit(code);
|
|
966
|
+
} else if (sub === "context") {
|
|
967
|
+
let scope = "project";
|
|
968
|
+
let slug = "";
|
|
969
|
+
let json = false;
|
|
970
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
971
|
+
const arg = rest[i];
|
|
972
|
+
if (arg === "--json") {
|
|
973
|
+
json = true;
|
|
974
|
+
} else if (arg === "--scope" && rest[i + 1]) {
|
|
975
|
+
scope = rest[i + 1];
|
|
976
|
+
i += 1;
|
|
977
|
+
} else if (arg.startsWith("--scope=")) {
|
|
978
|
+
scope = arg.slice("--scope=".length);
|
|
979
|
+
} else if (arg === "--slug" && rest[i + 1]) {
|
|
980
|
+
slug = rest[i + 1];
|
|
981
|
+
i += 1;
|
|
982
|
+
} else if (arg.startsWith("--slug=")) {
|
|
983
|
+
slug = arg.slice("--slug=".length);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
const scriptArgs = ["--scope", scope];
|
|
987
|
+
if (slug) {
|
|
988
|
+
scriptArgs.push("--slug", slug);
|
|
989
|
+
}
|
|
990
|
+
if (json) {
|
|
991
|
+
scriptArgs.push("--json");
|
|
992
|
+
}
|
|
993
|
+
const code = await runGuardrailsScript("req-context", scriptArgs);
|
|
994
|
+
process.exit(code);
|
|
956
995
|
} else {
|
|
957
|
-
throw new Error("Usage: req-analysis init | discover | promote");
|
|
996
|
+
throw new Error("Usage: req-analysis init | discover | promote | validate | context");
|
|
958
997
|
}
|
|
959
998
|
} catch (err) {
|
|
960
999
|
console.error(`❌ ${err.message}`);
|
package/lib/classify-change.js
CHANGED
|
@@ -26,12 +26,21 @@ const DEPENDENCY_SIGNALS = [
|
|
|
26
26
|
/\badd\s+package\b/i,
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
+
const VAGUE_SIGNALS = [
|
|
30
|
+
/\bimprove\b/i,
|
|
31
|
+
/\bmake\s+(?:it|this|things?)\s+better\b/i,
|
|
32
|
+
/\badd\s+(?:a\s+)?(?:interface|page|screen|ui|dashboard)\b/i,
|
|
33
|
+
/\b(?:somehow|something|stuff)\b/i,
|
|
34
|
+
/\bwithout\s+(?:criteria|details|spec)\b/i,
|
|
35
|
+
];
|
|
36
|
+
|
|
29
37
|
/**
|
|
30
38
|
* @param {{ description?: string, files?: string[] }} input
|
|
31
39
|
* @returns {{
|
|
32
40
|
* tier: "quick" | "simple" | "medium" | "complex",
|
|
33
41
|
* reasons: string[],
|
|
34
42
|
* next: string,
|
|
43
|
+
* suggestElicit: boolean,
|
|
35
44
|
* fileCount: number,
|
|
36
45
|
* }}
|
|
37
46
|
*/
|
|
@@ -45,6 +54,7 @@ export function classifyChange(input = {}) {
|
|
|
45
54
|
const hasComplex = COMPLEX_SIGNALS.some((re) => re.test(haystack));
|
|
46
55
|
const hasMedium = MEDIUM_SIGNALS.some((re) => re.test(haystack));
|
|
47
56
|
const hasNewDep = DEPENDENCY_SIGNALS.some((re) => re.test(haystack));
|
|
57
|
+
const hasVague = VAGUE_SIGNALS.some((re) => re.test(haystack));
|
|
48
58
|
|
|
49
59
|
if (hasComplex) {
|
|
50
60
|
reasons.push("sensitive surface or architecture signal in description/paths");
|
|
@@ -103,10 +113,15 @@ export function classifyChange(input = {}) {
|
|
|
103
113
|
'feature-init → full pipeline (+ /discuss, /plan; optional AppSec/QA on verify)',
|
|
104
114
|
};
|
|
105
115
|
|
|
116
|
+
if (hasVague) {
|
|
117
|
+
reasons.push("vague delivery language — consider /elicit before /specify");
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
return {
|
|
107
121
|
tier,
|
|
108
122
|
reasons,
|
|
109
123
|
next: nextByTier[tier],
|
|
124
|
+
suggestElicit: hasVague && !hasComplex,
|
|
110
125
|
fileCount,
|
|
111
126
|
};
|
|
112
127
|
}
|
|
@@ -123,5 +138,8 @@ export function formatClassifyChange(result) {
|
|
|
123
138
|
...result.reasons.map((r) => ` - ${r}`),
|
|
124
139
|
`Next: ${result.next}`,
|
|
125
140
|
];
|
|
141
|
+
if (result.suggestElicit) {
|
|
142
|
+
lines.push("Suggest: /elicit (structured Q&A) or /specify if scope is already clear");
|
|
143
|
+
}
|
|
126
144
|
return `${lines.join("\n")}\n`;
|
|
127
145
|
}
|
package/lib/constants.js
CHANGED
|
@@ -118,6 +118,8 @@ export const SCRIPT_ASSETS = [
|
|
|
118
118
|
{ file: "_memory_embed.py", remotePath: "scripts/_memory_embed.py" },
|
|
119
119
|
{ file: "episodes.py", remotePath: "scripts/episodes.py" },
|
|
120
120
|
{ file: "code_index.py", remotePath: "scripts/code_index.py" },
|
|
121
|
+
{ file: "validate_req_analysis.py", remotePath: "scripts/validate_req_analysis.py" },
|
|
122
|
+
{ file: "req_context.py", remotePath: "scripts/req_context.py" },
|
|
121
123
|
];
|
|
122
124
|
|
|
123
125
|
/** @type {{ file: string, remotePath: string }[]} */
|
package/lib/gates.js
CHANGED
|
@@ -34,6 +34,7 @@ const GATE_SCRIPTS = {
|
|
|
34
34
|
"validate-state": "validate_state.py",
|
|
35
35
|
"validate-traceability": "validate_traceability.py",
|
|
36
36
|
"validate-quick": "validate_quick.py",
|
|
37
|
+
"validate-req-analysis": "validate_req_analysis.py",
|
|
37
38
|
"analyze-artifacts": "analyze_artifacts.py",
|
|
38
39
|
"check-commit": "check_commit.py",
|
|
39
40
|
lessons: "lessons.py",
|
|
@@ -47,6 +48,7 @@ const AUX_SCRIPTS = {
|
|
|
47
48
|
"memory-retrieve": "memory_retrieve.py",
|
|
48
49
|
episodes: "episodes.py",
|
|
49
50
|
"code-index": "code_index.py",
|
|
51
|
+
"req-context": "req_context.py",
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
const GUARDRAILS_SCRIPTS = { ...GATE_SCRIPTS, ...AUX_SCRIPTS };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luizsantiago/spec-guardrails",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.1.1",
|
|
4
|
+
"description": "Governed spec-driven process kit for AI agents: phase guides, .specs/ memory, optional Python gates. Loop: specify, tasks, execute, verify. Cursor hooks (scope + shell policy) are optional; disable on lighter machines. Node 18+; Python 3.10+ for Brakes. Cursor, Claude, Copilot, Codex.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"spec-guardrails": "./index.js"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Assemble kickoff + requirements brief context for the Specify phase.
|
|
3
|
+
|
|
4
|
+
python3 req_context.py --scope project
|
|
5
|
+
python3 req_context.py --scope feature --slug settings-page
|
|
6
|
+
python3 req_context.py --json
|
|
7
|
+
|
|
8
|
+
Read-only: lists discovered sources and brief paths; does not mutate files.
|
|
9
|
+
Exit codes: 0 success, 2 usage error.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
PROJECT_DIR = Path(".specs/project")
|
|
20
|
+
KICKOFF_PATHS = (
|
|
21
|
+
Path("prd.md"),
|
|
22
|
+
Path("docs/brief.md"),
|
|
23
|
+
Path("docs/prd.md"),
|
|
24
|
+
PROJECT_DIR / "kickoff.md",
|
|
25
|
+
)
|
|
26
|
+
PROJECT_BRIEF = PROJECT_DIR / "requirements-brief.md"
|
|
27
|
+
FEATURE_BRIEFS = PROJECT_DIR / "feature-briefs"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def discover_sources(root: Path) -> list[dict[str, str | bool]]:
|
|
31
|
+
entries: list[dict[str, str | bool]] = []
|
|
32
|
+
seen: set[str] = set()
|
|
33
|
+
for rel in KICKOFF_PATHS:
|
|
34
|
+
key = rel.as_posix()
|
|
35
|
+
if key in seen:
|
|
36
|
+
continue
|
|
37
|
+
seen.add(key)
|
|
38
|
+
path = root / rel
|
|
39
|
+
entries.append({"path": key, "exists": path.is_file()})
|
|
40
|
+
return entries
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def resolve_brief(root: Path, scope: str, slug: str | None) -> Path | None:
|
|
44
|
+
if scope == "project":
|
|
45
|
+
candidate = root / PROJECT_BRIEF
|
|
46
|
+
return candidate if candidate.is_file() else None
|
|
47
|
+
|
|
48
|
+
if not slug:
|
|
49
|
+
briefs_root = root / FEATURE_BRIEFS
|
|
50
|
+
if not briefs_root.is_dir():
|
|
51
|
+
return None
|
|
52
|
+
matches = sorted(briefs_root.rglob("requirements-brief.md"))
|
|
53
|
+
return matches[0] if len(matches) == 1 else None
|
|
54
|
+
|
|
55
|
+
candidate = root / FEATURE_BRIEFS / slug / "requirements-brief.md"
|
|
56
|
+
return candidate if candidate.is_file() else None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def excerpt(path: Path, max_chars: int = 1200) -> str:
|
|
60
|
+
text = path.read_text(encoding="utf-8")
|
|
61
|
+
trimmed = text.strip()
|
|
62
|
+
if len(trimmed) <= max_chars:
|
|
63
|
+
return trimmed
|
|
64
|
+
return f"{trimmed[: max_chars - 3].rstrip()}..."
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def build_context(root: Path, scope: str, slug: str | None) -> dict:
|
|
68
|
+
sources = discover_sources(root)
|
|
69
|
+
brief = resolve_brief(root, scope, slug)
|
|
70
|
+
payload: dict = {
|
|
71
|
+
"scope": scope,
|
|
72
|
+
"sources": sources,
|
|
73
|
+
"brief_path": brief.relative_to(root).as_posix() if brief else None,
|
|
74
|
+
}
|
|
75
|
+
if brief:
|
|
76
|
+
payload["brief_excerpt"] = excerpt(brief)
|
|
77
|
+
if scope == "feature" and slug:
|
|
78
|
+
payload["slug"] = slug
|
|
79
|
+
return payload
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_markdown(ctx: dict) -> str:
|
|
83
|
+
lines = [
|
|
84
|
+
"# Requirements context",
|
|
85
|
+
"",
|
|
86
|
+
f"Scope: **{ctx['scope']}**",
|
|
87
|
+
"",
|
|
88
|
+
"## Kickoff sources",
|
|
89
|
+
"",
|
|
90
|
+
]
|
|
91
|
+
for entry in ctx["sources"]:
|
|
92
|
+
label = "found" if entry["exists"] else "missing"
|
|
93
|
+
lines.append(f"- [{label}] {entry['path']}")
|
|
94
|
+
|
|
95
|
+
lines.extend(["", "## Requirements brief", ""])
|
|
96
|
+
if ctx.get("brief_path"):
|
|
97
|
+
lines.append(f"- Path: `{ctx['brief_path']}`")
|
|
98
|
+
if ctx.get("brief_excerpt"):
|
|
99
|
+
lines.extend(["", "### Excerpt", "", "```markdown", ctx["brief_excerpt"], "```"])
|
|
100
|
+
else:
|
|
101
|
+
lines.append("- No requirements brief found for this scope.")
|
|
102
|
+
|
|
103
|
+
lines.extend(
|
|
104
|
+
[
|
|
105
|
+
"",
|
|
106
|
+
"## Next",
|
|
107
|
+
"",
|
|
108
|
+
"- Run `validate-req-analysis` on the brief before `/specify`",
|
|
109
|
+
"- Derive spec.md from the brief — do not re-ask resolved questions",
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
return "\n".join(lines) + "\n"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main(argv: list[str] | None = None) -> int:
|
|
116
|
+
parser = argparse.ArgumentParser(description="Requirements context for Specify")
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"--scope",
|
|
119
|
+
choices=("project", "feature"),
|
|
120
|
+
default="project",
|
|
121
|
+
help="Elicitation scope (default: project)",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--slug",
|
|
125
|
+
help="Feature slug when scope=feature (default: sole feature brief if only one exists)",
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument("--json", action="store_true", help="Emit JSON instead of markdown")
|
|
128
|
+
args = parser.parse_args(argv)
|
|
129
|
+
|
|
130
|
+
if args.scope == "feature" and args.slug and not args.slug.strip():
|
|
131
|
+
print("[req-context] USAGE - --slug must not be empty", file=sys.stderr)
|
|
132
|
+
return 2
|
|
133
|
+
|
|
134
|
+
ctx = build_context(Path("."), args.scope, args.slug.strip() if args.slug else None)
|
|
135
|
+
|
|
136
|
+
if args.json:
|
|
137
|
+
print(json.dumps(ctx, indent=2))
|
|
138
|
+
else:
|
|
139
|
+
print(format_markdown(ctx), end="")
|
|
140
|
+
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Structural gate for requirements briefs produced by /elicit.
|
|
3
|
+
|
|
4
|
+
python3 validate_req_analysis.py .specs/project/requirements-brief.md
|
|
5
|
+
python3 validate_req_analysis.py .specs/project/feature-briefs/settings-page/requirements-brief.md
|
|
6
|
+
|
|
7
|
+
Checks (markdown structure only):
|
|
8
|
+
* Required sections: Goal, Context sources, Owner approval
|
|
9
|
+
* Context sources lists at least one non-placeholder entry
|
|
10
|
+
* Open questions is empty or explicitly "- none"
|
|
11
|
+
* Owner approval indicates yes with a date
|
|
12
|
+
* No [NEEDS CLARIFICATION] or [OPEN QUESTION] markers remain
|
|
13
|
+
|
|
14
|
+
Exit codes: 0 pass, 1 blocking issues, 2 usage error.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from _common import Report, visible_markdown
|
|
25
|
+
|
|
26
|
+
GATE = "validate-req-analysis"
|
|
27
|
+
PROJECT_DIR = Path(".specs/project")
|
|
28
|
+
PROJECT_BRIEF = PROJECT_DIR / "requirements-brief.md"
|
|
29
|
+
FEATURE_BRIEFS = PROJECT_DIR / "feature-briefs"
|
|
30
|
+
|
|
31
|
+
HEADING = re.compile(r"^##\s+(.+?)\s*$", re.MULTILINE)
|
|
32
|
+
APPROVAL_YES = re.compile(
|
|
33
|
+
r"\b(?:approved|approval)\b[^\n]*:\s*(?:yes|approved|true)\b",
|
|
34
|
+
re.IGNORECASE,
|
|
35
|
+
)
|
|
36
|
+
APPROVAL_DATE = re.compile(
|
|
37
|
+
r"\bdate\b\s*:\s*(\d{4}-\d{2}-\d{2}|YYYY-MM-DD)",
|
|
38
|
+
re.IGNORECASE,
|
|
39
|
+
)
|
|
40
|
+
OPEN_NONE = re.compile(r"^\s*[-*]\s*(?:none|n/a)\s*$", re.IGNORECASE | re.MULTILINE)
|
|
41
|
+
CLARIFICATION = re.compile(r"\[(?:NEEDS CLARIFICATION|OPEN QUESTION)\]", re.IGNORECASE)
|
|
42
|
+
PLACEHOLDER_SOURCE = re.compile(
|
|
43
|
+
r"^\s*[-*]\s*\(\s*list every file",
|
|
44
|
+
re.IGNORECASE | re.MULTILINE,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _fail_usage(message: str, target: str = ".") -> None:
|
|
49
|
+
print(f"[{GATE}] USAGE - {message}", file=sys.stderr)
|
|
50
|
+
raise SystemExit(2)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def resolve_brief_path(raw: str | None, root: Path = Path(".")) -> Path:
|
|
54
|
+
if not raw:
|
|
55
|
+
candidate = root / PROJECT_BRIEF
|
|
56
|
+
if candidate.is_file():
|
|
57
|
+
return candidate
|
|
58
|
+
_fail_usage(
|
|
59
|
+
"no brief path given and .specs/project/requirements-brief.md is missing",
|
|
60
|
+
str(PROJECT_BRIEF),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
candidate = Path(raw).expanduser()
|
|
64
|
+
if candidate.is_absolute():
|
|
65
|
+
brief = candidate
|
|
66
|
+
else:
|
|
67
|
+
brief = root / candidate
|
|
68
|
+
|
|
69
|
+
if not brief.is_file():
|
|
70
|
+
_fail_usage(f"no such requirements brief: {raw}", raw or str(PROJECT_BRIEF))
|
|
71
|
+
|
|
72
|
+
rel = brief.resolve()
|
|
73
|
+
project_root = (root / PROJECT_DIR).resolve()
|
|
74
|
+
try:
|
|
75
|
+
rel.relative_to(project_root)
|
|
76
|
+
except ValueError:
|
|
77
|
+
_fail_usage(
|
|
78
|
+
f"brief must live under {PROJECT_DIR.as_posix()}/",
|
|
79
|
+
str(brief),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if brief.name != "requirements-brief.md":
|
|
83
|
+
_fail_usage(
|
|
84
|
+
"expected a requirements-brief.md file under .specs/project/",
|
|
85
|
+
str(brief),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return brief
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def section_body(text: str, title: str) -> str:
|
|
92
|
+
match = HEADING.search(text)
|
|
93
|
+
if not match:
|
|
94
|
+
return ""
|
|
95
|
+
|
|
96
|
+
pattern = re.compile(
|
|
97
|
+
rf"^##\s+{re.escape(title)}\s*$",
|
|
98
|
+
re.MULTILINE | re.IGNORECASE,
|
|
99
|
+
)
|
|
100
|
+
start = pattern.search(text)
|
|
101
|
+
if not start:
|
|
102
|
+
return ""
|
|
103
|
+
|
|
104
|
+
rest = text[start.end() :]
|
|
105
|
+
next_heading = HEADING.search(rest)
|
|
106
|
+
body = rest[: next_heading.start()] if next_heading else rest
|
|
107
|
+
return body.strip()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_report(brief_path: Path) -> Report:
|
|
111
|
+
report = Report(GATE, brief_path.as_posix())
|
|
112
|
+
text = brief_path.read_text(encoding="utf-8")
|
|
113
|
+
visible = visible_markdown(text)
|
|
114
|
+
|
|
115
|
+
for section in ("Goal", "Context sources", "Owner approval"):
|
|
116
|
+
if not section_body(text, section):
|
|
117
|
+
report.error(f"missing ## {section} section")
|
|
118
|
+
|
|
119
|
+
goal_body = section_body(text, "Goal")
|
|
120
|
+
if goal_body and len(goal_body.strip()) < 8:
|
|
121
|
+
report.error("Goal section is too short to be actionable")
|
|
122
|
+
|
|
123
|
+
sources_body = section_body(text, "Context sources")
|
|
124
|
+
if sources_body:
|
|
125
|
+
if PLACEHOLDER_SOURCE.search(sources_body):
|
|
126
|
+
report.error("Context sources still contains the scaffold placeholder")
|
|
127
|
+
elif not re.search(r"^\s*[-*]\s+\S", sources_body, re.MULTILINE):
|
|
128
|
+
report.error("Context sources must list at least one bullet entry")
|
|
129
|
+
else:
|
|
130
|
+
report.ok("Context sources lists at least one entry")
|
|
131
|
+
else:
|
|
132
|
+
report.error("Context sources section is empty")
|
|
133
|
+
|
|
134
|
+
open_body = section_body(text, "Open questions")
|
|
135
|
+
if open_body:
|
|
136
|
+
bullets = [
|
|
137
|
+
line.strip()
|
|
138
|
+
for line in open_body.splitlines()
|
|
139
|
+
if line.strip().startswith(("-", "*"))
|
|
140
|
+
]
|
|
141
|
+
if bullets and not all(OPEN_NONE.match(line) for line in bullets):
|
|
142
|
+
report.error(
|
|
143
|
+
'Open questions must be "- none" or empty before /specify'
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
report.ok("Open questions closed")
|
|
147
|
+
else:
|
|
148
|
+
report.ok("Open questions section absent or empty")
|
|
149
|
+
|
|
150
|
+
approval_body = section_body(text, "Owner approval")
|
|
151
|
+
if approval_body:
|
|
152
|
+
if not APPROVAL_YES.search(approval_body):
|
|
153
|
+
report.error('Owner approval must include "Approved: yes" (or equivalent)')
|
|
154
|
+
if APPROVAL_DATE.search(approval_body) and "YYYY-MM-DD" in approval_body:
|
|
155
|
+
report.error("Owner approval date is still the scaffold placeholder")
|
|
156
|
+
elif not re.search(r"\d{4}-\d{2}-\d{2}", approval_body):
|
|
157
|
+
report.warn("Owner approval has no YYYY-MM-DD date")
|
|
158
|
+
else:
|
|
159
|
+
report.ok("Owner approval recorded")
|
|
160
|
+
else:
|
|
161
|
+
report.error("Owner approval section is empty")
|
|
162
|
+
|
|
163
|
+
if CLARIFICATION.search(visible):
|
|
164
|
+
report.error("[NEEDS CLARIFICATION] or [OPEN QUESTION] marker remains")
|
|
165
|
+
else:
|
|
166
|
+
report.ok("no clarification markers in brief")
|
|
167
|
+
|
|
168
|
+
return report
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def main(argv: list[str] | None = None) -> int:
|
|
172
|
+
parser = argparse.ArgumentParser(description="Validate a requirements brief from /elicit")
|
|
173
|
+
parser.add_argument(
|
|
174
|
+
"brief",
|
|
175
|
+
nargs="?",
|
|
176
|
+
help="Path to requirements-brief.md (default: .specs/project/requirements-brief.md)",
|
|
177
|
+
)
|
|
178
|
+
parser.add_argument(
|
|
179
|
+
"--strict",
|
|
180
|
+
action="store_true",
|
|
181
|
+
help="Treat warnings as blocking failures",
|
|
182
|
+
)
|
|
183
|
+
args = parser.parse_args(argv)
|
|
184
|
+
|
|
185
|
+
brief = resolve_brief_path(args.brief)
|
|
186
|
+
report = build_report(brief)
|
|
187
|
+
return report.emit(strict=args.strict)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
raise SystemExit(main())
|
|
@@ -82,7 +82,7 @@ EXPLORE (optional) → ELICIT (optional) → SPECIFY → DISCUSS (conditional)
|
|
|
82
82
|
| Phase | Required | Reference | Sister skill | Gate |
|
|
83
83
|
| --- | --- | --- | --- | --- |
|
|
84
84
|
| **Explore** | Optional | `references/explore.md` | — | — |
|
|
85
|
-
| **Elicit** | Optional | `references/elicitation.md` |
|
|
85
|
+
| **Elicit** | Optional | `references/elicitation.md` | `validate-req-analysis` | — (before `/specify`; suggest-only entry) |
|
|
86
86
|
| **Constitution** | Once per project | `references/constitution.md` | — | — |
|
|
87
87
|
| **Specify** | Yes | `references/specify.md` | — | `validate_spec.py` |
|
|
88
88
|
| **Discuss** | Conditional | `references/discuss.md` | — | — |
|
|
@@ -136,8 +136,14 @@ Use the template from `req-analysis init`. Required sections:
|
|
|
136
136
|
|
|
137
137
|
Present the brief summary. Wait for explicit approval before `/specify`.
|
|
138
138
|
|
|
139
|
-
**
|
|
139
|
+
**Gate before `/specify` (Brakes mode):**
|
|
140
140
|
|
|
141
|
+
```bash
|
|
142
|
+
npx @luizsantiago/spec-guardrails req-analysis validate
|
|
143
|
+
# or: validate-req-analysis .specs/project/requirements-brief.md
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Checklist (Process mode — agent verifies when Python is unavailable):**
|
|
141
147
|
- [ ] Open questions is `- none` or empty
|
|
142
148
|
- [ ] Owner approval filled
|
|
143
149
|
- [ ] Context sources lists at least one input
|
|
@@ -160,6 +166,12 @@ Copy or link brief → `.specs/features/NNN-slug/requirements-brief.md` (optiona
|
|
|
160
166
|
|
|
161
167
|
Open `specify.md` — derive `spec.md` from brief; do **not** re-ask resolved questions.
|
|
162
168
|
|
|
169
|
+
Optional context bundle for Specify:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npx @luizsantiago/spec-guardrails req-analysis context --scope feature --slug settings-page
|
|
173
|
+
```
|
|
174
|
+
|
|
163
175
|
```bash
|
|
164
176
|
npx @luizsantiago/spec-guardrails req-analysis promote --scope feature
|
|
165
177
|
```
|