@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 CHANGED
@@ -5,170 +5,254 @@
5
5
 
6
6
  **Governed spec-driven development for AI coding agents.**
7
7
 
8
- Spec Guardrails is a process kit that installs phase guides, persistent project memory, and optional structural checks into your repository. Teams keep ownership of requirements and approval gates; agents follow a repeatable path from written intent to verified delivery.
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 it is
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
- The default loop is **Specify → Tasks → Execute → Verify → Archive**. You approve specs and task plans; the agent implements in waves and produces proof before work is considered done.
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
- Full narrative: [Overview](docs/guide/Overview.md) · [How it works](docs/guide/How-it-works.md)
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
- ## Why teams use it
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
- | Benefit | Outcome |
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
- Process vs Brakes: [FAQ](docs/guide/FAQ.md#process-vs-brakes) · Guarantees: [Guarantees matrix](docs/guide/Guarantees-matrix.md)
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
- In your project root:
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 the package; existing `.specs/` notes are preserved. Check readiness with `doctor`.
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 — CLI and install |
58
- | **Python 3.10+** | Optional — enables Brakes mode (automatic gates) |
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
- Platform setup: [Platform parity](docs/guide/Platform-parity.md) · Release notes: [CHANGELOG](docs/CHANGELOG.md)
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 you work day to day
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
- Interaction happens in **agent chat**, not the terminal. The agent runs CLI helpers and gates when needed.
105
+ ## It sizes the work before starting
67
106
 
68
- ### Core commands
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
- | Command | Use when |
109
+ | If the change is… | The agent runs |
71
110
  | --- | --- |
72
- | `/specify` | Starting any non-trivial feature — requirements in writing first |
73
- | `/elicit` | Kickoff or request is vague — structured Q&A before Specify *(optional)* |
74
- | `/tasks` | Spec approved — break work into a job list |
75
- | `/loop` | Implementation — one wave at a time |
76
- | `/verify` | All jobs done — independent proof *(prefer a fresh chat)* |
77
- | `/archive` | Feature validated — fold into project memory |
78
- | `/quick` | Tiny fix only (≤3 files, no design fork) |
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
- **Typical path:** `/specify` → approve → `/tasks` → approve → `/loop` → `/verify` → `/archive`
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
- When input is still exploratory, `/explore` or `/elicit` may come first. The agent suggests depth; it does not block Specify without approval.
122
+ The agent proposes the depth and explains why; you can always ask for more or less.
83
123
 
84
- Command reference: [Agent commands](docs/guide/agent-commands.md) · Entry paths: [Overview → Three ways to start](docs/guide/Overview.md#three-ways-to-start-pick-one)
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
- ### Optional capabilities
126
+ ---
87
127
 
88
- Enable when the work warrants them — most teams start with the core loop only.
128
+ ## What you get, layer by layer
89
129
 
90
- | Capability | Purpose |
91
- | --- | --- |
92
- | Memory search | Retrieve past specs, validations, and kickoff briefs |
93
- | Context guards | Scope check before edit or “done” (Cursor: hooks) |
94
- | Episodic memory | Session notes → lessons for future runs |
95
- | Code index | Lightweight brownfield file and symbol map |
96
- | Solution exploration | Compare implementation options before committing |
97
- | Sandbox / execution policy | Warn or block destructive shell commands and path drift |
98
- | Semantic retrieval | Search by meaning — off by default |
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
- Guides: [Memory](docs/guide/Memory.md) · [Brownfield context](docs/guide/brownfield-context.md) · [Safety & exploration](docs/guide/Overview.md)
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 | Role |
172
+ | Path | What it is |
107
173
  | --- | --- |
108
- | `.cursor/skills/` (+ Claude, Copilot, Codex trees) | Phase instructions for the agent |
109
- | `.specs/STATE.md` | Active feature and next step |
110
- | `.specs/features/NNN-slug/` | Spec, tasks, and validation per feature |
111
- | `.specs/guardrails/scripts/` | Python gates (Brakes mode) |
112
- | `.specs/config.yaml` | Optional project rules and execution policy |
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
- Architecture: [Skills and hub](docs/guide/skills-and-hub.md) · [Architecture](docs/guide/Architecture.md)
180
+ All plain text, all in git. Nothing is added to your dependencies or your build output.
115
181
 
116
182
  ---
117
183
 
118
- ## Documentation
184
+ ## Optional extras
119
185
 
120
- Start with the guide that matches your question; each page links deeper where needed.
186
+ Start with the core flow. Turn these on when the work calls for them.
121
187
 
122
- | Topic | Start here | Go deeper |
123
- | --- | --- | --- |
124
- | Orientation | [Overview](docs/guide/Overview.md) | [Concepts](docs/guide/concepts.md) |
125
- | First session | [Quick start](docs/guide/Quick-start.md) | [Agent commands](docs/guide/agent-commands.md) |
126
- | Process model | [How it works](docs/guide/How-it-works.md) | [Loop patterns](docs/guide/loop-patterns.md) |
127
- | Enforcement | [Gates](docs/guide/gates.md) | [Gates and guarantees](docs/guide/Gates-and-guarantees.md) |
128
- | Long-running projects | [Memory](docs/guide/Memory.md) | [Brownfield context](docs/guide/brownfield-context.md) |
129
- | Questions | [FAQ](docs/guide/FAQ.md) | [Stability policy](docs/guide/Stability-policy.md) |
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
- Full index: [docs/guide/README.md](docs/guide/README.md)
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
- ## Contributing
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
- We welcome focused improvements — skills, gates, CLI, docs, and tests. See [CONTRIBUTING.md](CONTRIBUTING.md) for layout, gate stability rules, and local checks.
203
+ → [Cursor hooks and sandbox](docs/guide/Cursor-hooks-and-sandbox.md) — what they do, symptoms, tuning, full disable
138
204
 
139
- ### Use Spec Guardrails to build your contribution
205
+ ---
206
+
207
+ ## Documentation
140
208
 
141
- The recommended workflow is to **dogfood the product**: install Spec Guardrails, describe your change through the agent phases, implement against approved artifacts, and verify before opening a PR.
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
- **In your own project or fork** — use the latest stable release from npm:
220
+ Full index: [docs/guide/README.md](docs/guide/README.md)
144
221
 
145
- ```bash
146
- npx @luizsantiago/spec-guardrails@latest install
147
- npx @luizsantiago/spec-guardrails doctor
148
- ```
222
+ ---
149
223
 
150
- Then in chat: `/specify` (or `/elicit` if scope is unclear) → `/tasks` → `/loop` → `/verify`. Your `.specs/` folder holds the spec and proof that guided the change.
224
+ ## Contributing
151
225
 
152
- **In this source repository** — work against the branch you are developing, not the published tarball:
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 run guardrails -- doctor
233
+ npm test
160
234
  ```
161
235
 
162
- Edit source under `skills/`, `lib/`, `scripts/`, and `rules/`; re-run `npm run guardrails -- install` after skill or gate changes. Run `npm test` before every PR.
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
- | Path | Role |
165
- | --- | --- |
166
- | `skills/` | Hub and sister skills shipped to consumers |
167
- | `skills/references/` | Phase procedures (`specify.md`, `elicit.md`, …) |
168
- | `scripts/` | Deterministic Python gates |
169
- | `test/` | Node install tests and Python gate suites |
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
- Gate changes follow the adversarial test policy in [CONTRIBUTING.md](CONTRIBUTING.md). Credits: [docs/guide/credits.md](docs/guide/credits.md)
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}`);
@@ -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.0.0",
4
- "description": "Spec-driven process kit for AI coding agents: write goals in .specs/, break into tasks, implement in waves, verify with proof. Process mode (Node) or Brakes mode (Node + Python gates). Works with Cursor, Claude, Copilot, and Codex.",
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` | — | — (v1 skill checklist; gate in 4.x wave 2) |
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
- **v1 checklist (no Python gate yet):**
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
  ```