@luizsantiago/spec-guardrails 3.9.0 → 4.0.0

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
@@ -3,156 +3,175 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@luizsantiago/spec-guardrails.svg)](https://www.npmjs.com/package/@luizsantiago/spec-guardrails)
4
4
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
5
 
6
- **Keep AI coding agents honest — specify the work, prove each step, verify independently.**
6
+ **Governed spec-driven development for AI coding agents.**
7
7
 
8
- npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **3.9.x**
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
9
 
10
- ---
10
+ npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **4.0.x**
11
11
 
12
- ## What is Spec Guardrails?
12
+ ---
13
13
 
14
- **Spec Guardrails** is a **process kit** for AI-assisted software development. It does not replace your app, framework, or tests. It gives your agent a repeatable way to work:
14
+ ## What it is
15
15
 
16
- 1. **Write down the goal** before coding (`spec.md`)
17
- 2. **Break work into small jobs** (`tasks.md`)
18
- 3. **Implement in waves** with real checks
19
- 4. **Verify with fresh eyes** — proof, not “trust me”
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:
20
17
 
21
- Everything important lives in **`.specs/`** in your repo so the project remembers decisions across chats and teammates.
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 |
22
23
 
23
- **You** approve specs and tasks. **The agent** follows phase guides and runs checks. **Gates** (optional Python scripts) can **stop** the workflow when paperwork or evidence is missing.
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.
24
25
 
25
- > **Plain-language guide:** [Overview](docs/guide/Overview.md) · [How it works](docs/guide/How-it-works.md) · [Quick start](docs/guide/Quick-start.md)
26
+ Full narrative: [Overview](docs/guide/Overview.md) · [How it works](docs/guide/How-it-works.md)
26
27
 
27
28
  ---
28
29
 
29
- ## What it is not
30
+ ## Why teams use it
31
+
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) |
30
40
 
31
- - Not a code generator or a new framework
32
- - Not a vector database or “AI memory” that reads your whole codebase
33
- - Not automatic push/merge/deploy — you stay in control of git tiers
34
- - Not a replacement for code review or product judgment
41
+ Process vs Brakes: [FAQ](docs/guide/FAQ.md#process-vs-brakes) · Guarantees: [Guarantees matrix](docs/guide/Guarantees-matrix.md)
35
42
 
36
43
  ---
37
44
 
38
- ## Install (once per project)
45
+ ## Install
46
+
47
+ In your project root:
39
48
 
40
49
  ```bash
41
50
  npx @luizsantiago/spec-guardrails install
42
51
  ```
43
52
 
44
- | Requirement | Purpose |
53
+ Re-run after upgrading the package; existing `.specs/` notes are preserved. Check readiness with `doctor`.
54
+
55
+ | Requirement | Role |
45
56
  | --- | --- |
46
57
  | **Node.js 18+** | Required — CLI and install |
47
- | **Python 3.10+** | Optional — enables **Brakes mode** (automatic gates). Without Python you still get the full workflow in **Process mode** |
48
-
49
- Re-run `install` after upgrading the package. Your `.specs/` notes and `STATE.md` are kept.
58
+ | **Python 3.10+** | Optional — enables Brakes mode (automatic gates) |
50
59
 
51
- Check readiness: `npx @luizsantiago/spec-guardrails doctor`
52
-
53
- Works with **Cursor, Claude Code, GitHub Copilot, OpenAI Codex**, and other agents via root `AGENTS.md`. See [Platform parity](docs/guide/Platform-parity.md).
60
+ Platform setup: [Platform parity](docs/guide/Platform-parity.md) · Release notes: [CHANGELOG](docs/CHANGELOG.md)
54
61
 
55
62
  ---
56
63
 
57
- ## How you use it day to day
64
+ ## How you work day to day
65
+
66
+ Interaction happens in **agent chat**, not the terminal. The agent runs CLI helpers and gates when needed.
58
67
 
59
- You talk to the agent in **chat**, not the terminal. These are **agent commands** (phrases the agent understands):
68
+ ### Core commands
60
69
 
61
- | Command | When to use |
70
+ | Command | Use when |
62
71
  | --- | --- |
63
- | `/specify` | Start a real feature — written requirements first |
64
- | `/tasks` | Shopping list of jobs after you approve the spec |
65
- | `/loop` | Implementone wave at a time |
66
- | `/verify` | Proof after the last job — use a **fresh** chat when possible |
67
- | `/quick` | Tiny fix only (≤3 files, no big design decisions) |
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) |
68
79
 
69
80
  **Typical path:** `/specify` → approve → `/tasks` → approve → `/loop` → `/verify` → `/archive`
70
81
 
71
- You do **not** need to memorize CLI commands. The agent runs gates and helpers for you when **Brakes mode** is available.
82
+ When input is still exploratory, `/explore` or `/elicit` may come first. The agent suggests depth; it does not block Specify without approval.
72
83
 
73
- **Full command reference:** [Agent commands](docs/guide/agent-commands.md)
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)
74
85
 
75
- ---
86
+ ### Optional capabilities
76
87
 
77
- ## Two modes (same product)
88
+ Enable when the work warrants them — most teams start with the core loop only.
78
89
 
79
- | Mode | You need | What changes |
80
- | --- | --- | --- |
81
- | **Process** | Node only | Full workflow + `.specs/` memory + phase skills |
82
- | **Brakes** | Node + Python | Same + scripts that **exit non-zero** when specs, tasks, or evidence are incomplete |
83
-
84
- Both are intentional. Add Python when you want automatic stop signs, not just checklists.
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 |
85
99
 
86
- Details: [FAQ Process vs Brakes](docs/guide/FAQ.md#process-vs-brakes)
100
+ Guides: [Memory](docs/guide/Memory.md) · [Brownfield context](docs/guide/brownfield-context.md) · [Safety & exploration](docs/guide/Overview.md)
87
101
 
88
102
  ---
89
103
 
90
- ## What lands in your repo
104
+ ## What lands in your repository
91
105
 
92
106
  | Path | Role |
93
107
  | --- | --- |
94
- | `.cursor/skills/` (and Claude, Copilot, Codex trees) | Instructions the agent reads per phase |
95
- | `.specs/STATE.md` | Where you are — active feature, next step |
96
- | `.specs/features/NNN-slug/` | Spec, tasks, validation for each feature |
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 |
97
111
  | `.specs/guardrails/scripts/` | Python gates (Brakes mode) |
98
- | `.specs/config.yaml` | Optional project rules and policy |
112
+ | `.specs/config.yaml` | Optional project rules and execution policy |
113
+
114
+ Architecture: [Skills and hub](docs/guide/skills-and-hub.md) · [Architecture](docs/guide/Architecture.md)
99
115
 
100
116
  ---
101
117
 
102
- ## Optional capabilities (use when you need them)
118
+ ## Documentation
103
119
 
104
- Most projects start with Specify Tasks Loop Verify only. Turn these on when the work warrants it:
120
+ Start with the guide that matches your question; each page links deeper where needed.
105
121
 
106
- | Capability | Plain purpose | Learn more |
122
+ | Topic | Start here | Go deeper |
107
123
  | --- | --- | --- |
108
- | **Memory search** | Find text in past specs and validations | [Memory guide](docs/guide/Memory.md) |
109
- | **Context guards** | Check scope before edit or “done” | [Agent commands](docs/guide/agent-commands.md) · **Cursor:** auto via hooks |
110
- | **Episodic memory** | Session notes episodic → lessons | [Memory → Episodes](docs/guide/Memory.md#episodic-memory-lifecycle) |
111
- | **Code index** | Lightweight brownfield file/symbol map | [Brownfield context](docs/guide/brownfield-context.md) |
112
- | **Sandbox policy** | Block/warn destructive shell commands | [Overview → Safety](docs/guide/Overview.md#safety-and-limits) |
113
- | **Execution policy** | Limit paths, retries, dangerous ops | [Overview → Safety](docs/guide/Overview.md#safety-and-limits) |
114
- | **Solution exploration** | Compare two+ implementations before committing | [Overview → Exploration](docs/guide/Overview.md#optional-exploration-mode) |
115
- | **Semantic retrieval** | Search by meaning (needs OpenAI or Ollama) | [Memory → Semantic](docs/guide/Memory.md#semantic-search-optional) |
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) |
116
130
 
117
- **Default:** semantic search is **off**. FTS + graph search work without any API.
131
+ Full index: [docs/guide/README.md](docs/guide/README.md)
118
132
 
119
133
  ---
120
134
 
121
- ## Documentation
135
+ ## Contributing
122
136
 
123
- | Start here | Best for |
124
- | --- | --- |
125
- | **[Overview](docs/guide/Overview.md)** | Complete simple picture — workflow, layers, team use |
126
- | [Quick start](docs/guide/Quick-start.md) | First ten minutes |
127
- | [How it works](docs/guide/How-it-works.md) | Story from idea to archive |
128
- | [Memory](docs/guide/Memory.md) | `.specs/` memory and search — when and how much |
129
- | [FAQ](docs/guide/FAQ.md) | Common questions |
137
+ We welcome focused improvements skills, gates, CLI, docs, and tests. See [CONTRIBUTING.md](CONTRIBUTING.md) for layout, gate stability rules, and local checks.
130
138
 
131
- | Go deeper | Best for |
132
- | --- | --- |
133
- | [Agent commands](docs/guide/agent-commands.md) | Every `/specify`, `/loop`, CLI helper |
134
- | [Guarantees matrix](docs/guide/Guarantees-matrix.md) | Product promises → mechanisms |
135
- | [Gates](docs/guide/gates.md) | What each gate checks |
136
- | [Architecture](docs/guide/Architecture.md) | Core vs platform adapters |
137
- | [Concepts](docs/guide/concepts.md) | Spec-driven, loop, graph, tiers |
138
- | [Changelog](docs/CHANGELOG.md) | Version history |
139
+ ### Use Spec Guardrails to build your contribution
139
140
 
140
- Doc index: [docs/guide/README.md](docs/guide/README.md)
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.
141
142
 
142
- ---
143
+ **In your own project or fork** — use the latest stable release from npm:
144
+
145
+ ```bash
146
+ npx @luizsantiago/spec-guardrails@latest install
147
+ npx @luizsantiago/spec-guardrails doctor
148
+ ```
149
+
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.
143
151
 
144
- ## Upgrading
152
+ **In this source repository** — work against the branch you are developing, not the published tarball:
145
153
 
146
154
  ```bash
147
- npx @luizsantiago/spec-guardrails install
155
+ git clone https://github.com/luizssantiago92/spec-guardrails.git
156
+ cd spec-guardrails
157
+ npm install
158
+ npm run guardrails -- install
159
+ npm run guardrails -- doctor
148
160
  ```
149
161
 
150
- See [CHANGELOG](docs/CHANGELOG.md) for release notes.
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.
163
+
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 |
170
+
171
+ Gate changes follow the adversarial test policy in [CONTRIBUTING.md](CONTRIBUTING.md). Credits: [docs/guide/credits.md](docs/guide/credits.md)
151
172
 
152
173
  ---
153
174
 
154
- ## Contributing · Credits · License
175
+ ## License
155
176
 
156
- - [CONTRIBUTING.md](CONTRIBUTING.md)
157
- - [Credits](docs/guide/credits.md)
158
- - MIT — see [LICENSE](LICENSE)
177
+ MIT — see [LICENSE](LICENSE).
package/index.js CHANGED
@@ -35,6 +35,11 @@ import {
35
35
  listWorkspaces,
36
36
  prepareWorkspaces,
37
37
  } from "./lib/workspace-isolation.js";
38
+ import {
39
+ formatDiscoverReport,
40
+ formatPromoteMessage,
41
+ reqAnalysisInit,
42
+ } from "./lib/req-analysis.js";
38
43
  import {
39
44
  formatExplorationInit,
40
45
  formatExplorationStatus,
@@ -76,6 +81,12 @@ Commands:
76
81
  feature-init "<description>" Allocate NNN-slug feature, STATE, local branch (Tier 0)
77
82
  [--no-branch] Skip git checkout -b
78
83
  [--no-spec] Skip spec.md stub
84
+ req-analysis init "<description>" Scaffold requirements brief (/elicit phase)
85
+ [--scope project|feature] project = macro brief; feature = one delivery (default: feature)
86
+ [--force] Replace existing brief scaffold
87
+ req-analysis discover List local kickoff sources (prd.md, kickoff.md, …)
88
+ req-analysis promote Print next steps after brief approval
89
+ [--scope project|feature] Match the brief scope
79
90
  archive-feature [feature] Fold verified feature into ROADMAP + domain spec; reset STATE
80
91
  [--domain <slug>] Domain folder under .specs/domains/ (default: feature slug)
81
92
  [--skip-verify] Skip validate-state (tests / recovery only)
@@ -890,6 +901,65 @@ if (command === "--version" || command === "-v" || command === "version") {
890
901
  console.error(`❌ ${err.message}`);
891
902
  process.exit(1);
892
903
  }
904
+ } else if (command === "req-analysis") {
905
+ try {
906
+ const sub = args[0];
907
+ const rest = args.slice(1);
908
+
909
+ if (sub === "init") {
910
+ let scope = "feature";
911
+ let force = false;
912
+ const positional = [];
913
+ for (let i = 0; i < rest.length; i += 1) {
914
+ const arg = rest[i];
915
+ if (arg === "--force") {
916
+ force = true;
917
+ } else if (arg === "--scope" && rest[i + 1]) {
918
+ scope = rest[i + 1];
919
+ i += 1;
920
+ } else if (arg.startsWith("--scope=")) {
921
+ scope = arg.slice("--scope=".length);
922
+ } else if (!arg.startsWith("--")) {
923
+ positional.push(arg);
924
+ }
925
+ }
926
+ const description = positional.join(" ").trim();
927
+ if (scope === "feature" && !description) {
928
+ throw new Error(
929
+ 'Usage: req-analysis init "<description>" [--scope project|feature] [--force]',
930
+ );
931
+ }
932
+ const result = await reqAnalysisInit(description, { scope, force });
933
+ console.log(`✅ Elicitation scaffold (${result.scope})`);
934
+ for (const briefPath of result.paths) {
935
+ console.log(` ${briefPath}`);
936
+ }
937
+ console.log("");
938
+ console.log(await formatDiscoverReport(process.cwd()));
939
+ } else if (sub === "discover") {
940
+ console.log(await formatDiscoverReport(process.cwd()));
941
+ } else if (sub === "promote") {
942
+ let scope = "feature";
943
+ let description = "";
944
+ for (let i = 0; i < rest.length; i += 1) {
945
+ const arg = rest[i];
946
+ if (arg.startsWith("--scope=")) {
947
+ scope = arg.slice("--scope=".length);
948
+ } else if (arg === "--scope" && rest[i + 1]) {
949
+ scope = rest[i + 1];
950
+ i += 1;
951
+ } else if (!arg.startsWith("--")) {
952
+ description = `${description} ${arg}`.trim();
953
+ }
954
+ }
955
+ console.log(formatPromoteMessage({ scope, description }));
956
+ } else {
957
+ throw new Error("Usage: req-analysis init | discover | promote");
958
+ }
959
+ } catch (err) {
960
+ console.error(`❌ ${err.message}`);
961
+ process.exit(1);
962
+ }
893
963
  } else if (command === "classify-change") {
894
964
  try {
895
965
  let json = false;
package/lib/constants.js CHANGED
@@ -77,6 +77,7 @@ export const SKILL_ASSETS = [
77
77
  /** @type {{ file: string, remotePath: string }[]} */
78
78
  export const REFERENCE_ASSETS = [
79
79
  { file: "explore.md", remotePath: "skills/references/explore.md" },
80
+ { file: "elicitation.md", remotePath: "skills/references/elicitation.md" },
80
81
  { file: "solution-exploration.md", remotePath: "skills/references/solution-exploration.md" },
81
82
  { file: "project-init.md", remotePath: "skills/references/project-init.md" },
82
83
  { file: "constitution.md", remotePath: "skills/references/constitution.md" },
@@ -0,0 +1,274 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { slugifyDescription } from "./feature.js";
5
+ import { ensureDir, writeFileSafe } from "./fs-utils.js";
6
+ import { NPX } from "./constants.js";
7
+
8
+ export const PROJECT_DIR = ".specs/project";
9
+ export const KICKOFF_FILENAME = "kickoff.md";
10
+ export const PROJECT_BRIEF_FILENAME = "requirements-brief.md";
11
+ export const FEATURE_BRIEFS_DIR = "feature-briefs";
12
+
13
+ /** @type {readonly string[]} */
14
+ export const DEFAULT_KICKOFF_DISCOVERY_PATHS = [
15
+ "prd.md",
16
+ "docs/brief.md",
17
+ "docs/prd.md",
18
+ path.join(PROJECT_DIR, KICKOFF_FILENAME),
19
+ ];
20
+
21
+ /**
22
+ * @param {string} scope
23
+ * @returns {"project" | "feature"}
24
+ */
25
+ export function normalizeScope(scope) {
26
+ const value = (scope ?? "feature").toLowerCase();
27
+ if (value === "project" || value === "feature") {
28
+ return value;
29
+ }
30
+ throw new Error('Scope must be "project" or "feature".');
31
+ }
32
+
33
+ /**
34
+ * @param {"project" | "feature"} scope
35
+ * @param {string} slug
36
+ * @param {string} [description]
37
+ * @returns {string}
38
+ */
39
+ export function briefPathForScope(scope, slug, description = "") {
40
+ if (scope === "project") {
41
+ return path.posix.join(PROJECT_DIR, PROJECT_BRIEF_FILENAME);
42
+ }
43
+ const featureSlug = slug || slugifyDescription(description || "feature");
44
+ return path.posix.join(
45
+ PROJECT_DIR,
46
+ FEATURE_BRIEFS_DIR,
47
+ featureSlug,
48
+ PROJECT_BRIEF_FILENAME,
49
+ );
50
+ }
51
+
52
+ /**
53
+ * @param {string} filePath
54
+ * @returns {Promise<boolean>}
55
+ */
56
+ async function pathExists(filePath) {
57
+ try {
58
+ await fs.access(filePath);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * @param {string} cwd
67
+ * @param {string[]} [extraPaths]
68
+ * @returns {Promise<Array<{ path: string, exists: boolean }>>}
69
+ */
70
+ export async function discoverKickoffSources(cwd, extraPaths = []) {
71
+ const candidates = [...DEFAULT_KICKOFF_DISCOVERY_PATHS, ...extraPaths];
72
+ const seen = new Set();
73
+ /** @type {Array<{ path: string, exists: boolean }>} */
74
+ const results = [];
75
+
76
+ for (const relative of candidates) {
77
+ const normalized = relative.replace(/\\/g, "/");
78
+ if (seen.has(normalized)) {
79
+ continue;
80
+ }
81
+ seen.add(normalized);
82
+ const absolute = path.join(cwd, relative);
83
+ let exists = false;
84
+ try {
85
+ await fs.access(absolute);
86
+ exists = true;
87
+ } catch {
88
+ exists = false;
89
+ }
90
+ results.push({ path: normalized, exists });
91
+ }
92
+
93
+ return results;
94
+ }
95
+
96
+ /**
97
+ * @param {"project" | "feature"} scope
98
+ * @param {string} description
99
+ * @returns {string}
100
+ */
101
+ export function buildBriefTemplate(scope, description) {
102
+ const goal =
103
+ description.trim() || (scope === "project" ? "Project goal" : "Feature goal");
104
+
105
+ const featureSection =
106
+ scope === "project"
107
+ ? `
108
+ ## Feature candidates
109
+
110
+ | Suggested slug | Goal | Priority |
111
+ | --- | --- | --- |
112
+ | 001-example | (one line) | P1 |
113
+
114
+ `
115
+ : "";
116
+
117
+ return `# Requirements brief: ${goal}
118
+
119
+ ## Goal
120
+
121
+ ${goal}
122
+
123
+ ## Context sources
124
+
125
+ - (list every file or chat paste used)
126
+
127
+ ## Current state
128
+
129
+ - (what exists in repo today — brownfield notes)
130
+
131
+ ## Capabilities
132
+
133
+ - (product-language outcomes when done)
134
+
135
+ ## Interaction details
136
+
137
+ - (UI screens/actions or API contracts — or "n/a")
138
+
139
+ ## Constraints & out of scope
140
+
141
+ - In scope: …
142
+ - Out of scope: …
143
+
144
+ ## Resolved questions
145
+
146
+ ### D-001: (question one line)
147
+
148
+ - **Options considered**: A) … B) …
149
+ - **Decision**: …
150
+ - **Rationale**: …
151
+ - **Date**: YYYY-MM-DD
152
+
153
+ ## Open questions
154
+
155
+ - none
156
+
157
+ ${featureSection}## Owner approval
158
+
159
+ - Approved: yes
160
+ - Date: YYYY-MM-DD
161
+ `;
162
+ }
163
+
164
+ /**
165
+ * @param {string} description
166
+ * @param {{ scope?: string, cwd?: string, force?: boolean }} [options]
167
+ * @returns {Promise<{ scope: "project" | "feature", paths: string[], discovered: Array<{ path: string, exists: boolean }> }>}
168
+ */
169
+ export async function reqAnalysisInit(description, options = {}) {
170
+ const cwd = options.cwd ?? process.cwd();
171
+ const scope = normalizeScope(options.scope ?? "feature");
172
+ const slug = scope === "feature" ? slugifyDescription(description || "feature") : "";
173
+
174
+ if (!description.trim() && scope === "feature") {
175
+ throw new Error(
176
+ 'Description is required for feature scope. Example: req-analysis init "settings page" --scope feature',
177
+ );
178
+ }
179
+
180
+ const briefPath = briefPathForScope(scope, slug, description);
181
+ const kickoffRel = path.posix.join(PROJECT_DIR, KICKOFF_FILENAME);
182
+ const discovered = await discoverKickoffSources(cwd);
183
+
184
+ await ensureDir(path.join(cwd, ...path.dirname(briefPath).split("/")));
185
+
186
+ const briefFull = path.join(cwd, ...briefPath.split("/"));
187
+ const briefExists = await pathExists(briefFull);
188
+ if (briefExists && !options.force) {
189
+ throw new Error(
190
+ `${briefPath} already exists — use --force to replace the scaffold`,
191
+ );
192
+ }
193
+
194
+ await writeFileSafe(briefFull, buildBriefTemplate(scope, description));
195
+
196
+ /** @type {string[]} */
197
+ const paths = [briefPath];
198
+
199
+ if (scope === "project") {
200
+ const kickoffFull = path.join(cwd, ...kickoffRel.split("/"));
201
+ const kickoffExists = await pathExists(kickoffFull);
202
+ if (!kickoffExists) {
203
+ await ensureDir(path.join(cwd, PROJECT_DIR));
204
+ await writeFileSafe(
205
+ kickoffFull,
206
+ `# Kickoff brief
207
+
208
+ (Paste product vision, goals, or export from your notes here.)
209
+
210
+ ## Goal
211
+
212
+ ${description.trim() || "(one sentence — what we are building)"}
213
+
214
+ ## Notes
215
+
216
+ -
217
+ `,
218
+ );
219
+ paths.push(kickoffRel);
220
+ }
221
+ }
222
+
223
+ return { scope, paths, discovered };
224
+ }
225
+
226
+ /**
227
+ * @param {{ scope?: string, briefPath?: string, description?: string }} [options]
228
+ * @returns {string}
229
+ */
230
+ export function formatPromoteMessage(options = {}) {
231
+ const scope = normalizeScope(options.scope ?? "feature");
232
+
233
+ if (scope === "project") {
234
+ return [
235
+ "Project brief ready — next steps:",
236
+ " 1. Owner approves .specs/project/requirements-brief.md",
237
+ " 2. Update .specs/project/ROADMAP.md with feature candidates",
238
+ ` 3. Pick a feature → ${NPX("req-analysis init \"…\" --scope feature")} or /specify`,
239
+ ` 4. ${NPX("memory-index rebuild")} — index kickoff + brief`,
240
+ ].join("\n");
241
+ }
242
+
243
+ const desc = options.description?.trim() || "feature description";
244
+ return [
245
+ "Feature brief ready — next steps:",
246
+ " 1. Owner approves the requirements brief",
247
+ ` 2. ${NPX(`feature-init "${desc}"`)}`,
248
+ " 3. /specify — derive spec.md from brief (do not re-ask resolved questions)",
249
+ ` 4. ${NPX("memory-index rebuild")}`,
250
+ ].join("\n");
251
+ }
252
+
253
+ /**
254
+ * @param {string} cwd
255
+ * @returns {Promise<string>}
256
+ */
257
+ export async function formatDiscoverReport(cwd) {
258
+ const discovered = await discoverKickoffSources(cwd);
259
+ const lines = ["Kickoff source discovery:", ""];
260
+
261
+ for (const entry of discovered) {
262
+ lines.push(` ${entry.exists ? "[found]" : "[missing]"} ${entry.path}`);
263
+ }
264
+
265
+ const found = discovered.filter((entry) => entry.exists);
266
+ if (found.length === 0) {
267
+ lines.push("");
268
+ lines.push(
269
+ " No kickoff file yet — paste brief in chat or create .specs/project/kickoff.md",
270
+ );
271
+ }
272
+
273
+ return lines.join("\n");
274
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luizsantiago/spec-guardrails",
3
- "version": "3.9.0",
3
+ "version": "4.0.0",
4
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.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "scripts": {
13
13
  "guardrails": "node index.js",
14
14
  "test": "npm run test:node && npm run test:gates",
15
- "test:node": "node --test test/install.test.js test/test_feature_init.test.js test/test_config.test.js test/test_archive.test.js test/test_delta_merge.test.js test/test_presets.test.js test/test_brownfield.test.js test/test_doctor.test.js test/test_token_cost.test.js test/test_next_steps.test.js test/test_classify_change.test.js test/test_feature_status.test.js test/test_agent_contract.test.js test/test_gates_python.test.js test/test_specs_utils.test.js test/test_validation_verdict.test.js test/test_execution_policy.test.js test/test_workspace_isolation.test.js test/test_adapter_registry.test.js test/test_context_guard.test.js test/test_solution_exploration.test.js test/test_memory_doctor.test.js test/test_cursor_hooks.test.js test/test_sandbox_policy.test.js",
15
+ "test:node": "node --test test/install.test.js test/test_feature_init.test.js test/test_config.test.js test/test_archive.test.js test/test_delta_merge.test.js test/test_presets.test.js test/test_brownfield.test.js test/test_doctor.test.js test/test_token_cost.test.js test/test_next_steps.test.js test/test_classify_change.test.js test/test_feature_status.test.js test/test_agent_contract.test.js test/test_gates_python.test.js test/test_specs_utils.test.js test/test_validation_verdict.test.js test/test_execution_policy.test.js test/test_workspace_isolation.test.js test/test_adapter_registry.test.js test/test_context_guard.test.js test/test_solution_exploration.test.js test/test_memory_doctor.test.js test/test_cursor_hooks.test.js test/test_sandbox_policy.test.js test/test_req_analysis.test.js",
16
16
  "test:gates": "node test/run-gate-tests.mjs",
17
17
  "prepublishOnly": "npm test"
18
18
  },
@@ -41,6 +41,15 @@ SECTION_HEADING = re.compile(r"^##\s+(.+)$", re.MULTILINE)
41
41
 
42
42
  LESSON_STATUSES = {"approved", "graduated", "confirmed"}
43
43
 
44
+ PROJECT_DIR = SPECS_DIR / "project"
45
+ KICKOFF_DISCOVERY_PATHS = (
46
+ Path("prd.md"),
47
+ Path("docs/brief.md"),
48
+ Path("docs/prd.md"),
49
+ PROJECT_DIR / "kickoff.md",
50
+ PROJECT_DIR / "requirements-brief.md",
51
+ )
52
+
44
53
 
45
54
  def fail(message: str, code: int = EXIT_FAILED) -> int:
46
55
  print(f"[{GATE}] FAIL - {DB_PATH}")
@@ -404,6 +413,58 @@ def index_lessons(conn: sqlite3.Connection, now: str) -> tuple[int, int]:
404
413
  return entity_count, chunk_count
405
414
 
406
415
 
416
+ def index_kickoff_docs(conn: sqlite3.Connection, now: str) -> tuple[int, int]:
417
+ entity_count = 0
418
+ chunk_count = 0
419
+ seen: set[str] = set()
420
+
421
+ for rel_path in KICKOFF_DISCOVERY_PATHS:
422
+ path_key = str(rel_path).replace("\\", "/")
423
+ if path_key in seen:
424
+ continue
425
+ seen.add(path_key)
426
+
427
+ doc_path = Path(path_key)
428
+ if not doc_path.is_file():
429
+ continue
430
+
431
+ entity_id = f"kickoff:{path_key.replace('/', ':')}"
432
+ label = path_key
433
+ text = doc_path.read_text(encoding="utf-8")
434
+ upsert_entity(conn, entity_id, "kickoff", label, path_key, now)
435
+ entity_count += 1
436
+ chunk_count += chunk_markdown_sections(
437
+ conn,
438
+ "project",
439
+ doc_path,
440
+ text,
441
+ "kickoff",
442
+ entity_id,
443
+ now,
444
+ )
445
+
446
+ feature_briefs_root = PROJECT_DIR / "feature-briefs"
447
+ if feature_briefs_root.is_dir():
448
+ for brief_path in sorted(feature_briefs_root.rglob("requirements-brief.md")):
449
+ rel = brief_path.as_posix()
450
+ slug = brief_path.parent.name
451
+ entity_id = f"feature-brief:{slug}"
452
+ text = brief_path.read_text(encoding="utf-8")
453
+ upsert_entity(conn, entity_id, "feature-brief", slug, rel, now)
454
+ entity_count += 1
455
+ chunk_count += chunk_markdown_sections(
456
+ conn,
457
+ slug,
458
+ brief_path,
459
+ text,
460
+ "feature-brief",
461
+ entity_id,
462
+ now,
463
+ )
464
+
465
+ return entity_count, chunk_count
466
+
467
+
407
468
  def index_episodes(conn: sqlite3.Connection, now: str) -> tuple[int, int]:
408
469
  episodes_path = SPECS_DIR / "state" / "episodes.json"
409
470
  if not episodes_path.is_file():
@@ -586,6 +647,9 @@ def rebuild(json_output: bool = False) -> int:
586
647
  conn, feature_id, tasks_path, tasks_text, now
587
648
  )
588
649
 
650
+ kickoff_entities, kickoff_chunks = index_kickoff_docs(conn, now)
651
+ entity_count += kickoff_entities
652
+ chunk_count += kickoff_chunks
589
653
  lesson_entities, lesson_chunks = index_lessons(conn, now)
590
654
  entity_count += lesson_entities
591
655
  chunk_count += lesson_chunks
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: agent-architecture
3
- description: Spec-Driven Development hub for AI-assisted engineering. Progressive disclosure (~70% fewer skill tokens vs dumping the full kit). Adaptive phases with Python gates, independent verifier, discrimination sensor, evidence-or-zero, and .specs/ memory. Triggers on "specify feature", "design", "break into tasks", "implement", "verify", "quick fix", "resume work", "handoff".
3
+ description: Spec-Driven Development hub for AI-assisted engineering. Progressive disclosure (~70% fewer skill tokens vs dumping the full kit). Adaptive phases with Python gates, independent verifier, discrimination sensor, evidence-or-zero, and .specs/ memory. Triggers on "specify feature", "elicit", "design", "break into tasks", "implement", "verify", "quick fix", "resume work", "handoff".
4
4
  ---
5
5
 
6
6
  # Agent Architecture (Hub)
@@ -41,6 +41,7 @@ Structural gates run **before** owner review, so they cannot drift when the mode
41
41
  | When | Command |
42
42
  | --- | --- |
43
43
  | Before `/specify` (Medium+) | `npx @luizsantiago/spec-guardrails feature-init "<description>"` (Tier 0) |
44
+ | Optional elicitation (vague kickoff or request) | `/elicit` → `req-analysis init` — **suggested only**, never blocks Specify |
44
45
  | Optional project config | `init-config --preset node-ts` or `install --preset python` (see `preset list`) |
45
46
  | Before confirming a spec | `python3 .specs/guardrails/scripts/validate_spec.py [feature]` |
46
47
  | Before approving tasks | `python3 .specs/guardrails/scripts/analyze_artifacts.py [feature]` |
@@ -75,12 +76,13 @@ A **non-zero exit means STOP** — fix the artifact, then re-run the gate. Never
75
76
  ## Phase Map
76
77
 
77
78
  ```
78
- EXPLORE (optional) → SPECIFY → DISCUSS (conditional) → DESIGN (optional) → TASKS (optional) → ANALYZE → EXECUTE (loop) → VERIFY → ARCHIVE
79
+ EXPLORE (optional) → ELICIT (optional) → SPECIFY → DISCUSS (conditional) → DESIGN (optional) → TASKS (optional) → ANALYZE → EXECUTE (loop) → VERIFY → ARCHIVE
79
80
  ```
80
81
 
81
82
  | Phase | Required | Reference | Sister skill | Gate |
82
83
  | --- | --- | --- | --- | --- |
83
84
  | **Explore** | Optional | `references/explore.md` | — | — |
85
+ | **Elicit** | Optional | `references/elicitation.md` | — | — (v1 skill checklist; gate in 4.x wave 2) |
84
86
  | **Constitution** | Once per project | `references/constitution.md` | — | — |
85
87
  | **Specify** | Yes | `references/specify.md` | — | `validate_spec.py` |
86
88
  | **Discuss** | Conditional | `references/discuss.md` | — | — |
@@ -131,6 +133,7 @@ Complexity determines depth. Do not run every phase on every change.
131
133
  - **Design is skipped** when there are no architectural decisions and no new patterns.
132
134
  - **Tasks is skipped** when there are ≤3 obvious steps.
133
135
  - **Discuss is triggered inside Specify** when the feature touches persistence, external calls, auth, payments, concurrency, or state transitions, or when the owner's intent is ambiguous.
136
+ - **Elicit is suggested (never required)** when a kickoff brief exists without an approved project brief, or when the owner's request is vague ("add interface", "improve X") — see `references/elicitation.md`. If the owner chooses `/specify` directly, proceed.
134
137
  - **Safety valve** — Even when Tasks is skipped, Execute starts by listing atomic steps inline. If that listing reveals more than 5 steps or real dependencies, STOP and create a formal `tasks.md`; the Tasks phase was skipped in error.
135
138
 
136
139
  When in doubt, start at **Medium** and drop phases only with owner approval.
@@ -145,6 +148,9 @@ When in doubt, start at **Medium** and drop phases only with owner approval.
145
148
  | `.specs/project/PROJECT.md` | Vision, stack, constraints (when the project defines them) |
146
149
  | `.specs/project/CONSTITUTION.md` | Governing principles (when Constitution ran) |
147
150
  | `.specs/project/ROADMAP.md` | Milestones and feature status |
151
+ | `.specs/project/kickoff.md` | Owner kickoff brief (paste or file — optional) |
152
+ | `.specs/project/requirements-brief.md` | Project-level elicitation output (when `/elicit` project ran) |
153
+ | `.specs/project/feature-briefs/[slug]/requirements-brief.md` | Feature-level elicitation (when `/elicit` feature ran) |
148
154
  | `.specs/config.yaml` | Optional project context and per-phase rules |
149
155
  | `.specs/domains/[domain]/spec.md` | Long-lived domain truth after Archive |
150
156
  | `.specs/quick/NNN-slug/` | Quick-mode tasks and summaries |
@@ -212,6 +218,7 @@ Project rules: `.cursor/rules/engineering-baseline.mdc` (always applied in Curso
212
218
  | Command | Reference | Action |
213
219
  | --- | --- | --- |
214
220
  | `/explore` | `references/explore.md` | Think through ideas before Specify |
221
+ | `/elicit` | `references/elicitation.md` | Structured Q&A before Specify — project or feature scope |
215
222
  | `/project-init` | `references/project-init.md` | Brownfield: map repo → PROJECT + domain stubs |
216
223
  | `/constitution` | `references/constitution.md` | Create project governing principles |
217
224
  | `/specify` | `references/specify.md` | `feature-init` then requirements; EARS; delta specs |
@@ -0,0 +1,197 @@
1
+ # Elicitation
2
+
3
+ Structured requirements discovery **before** `/specify`. Reads kickoff briefs, the local repo, and owner answers — then writes a **requirements brief** the Specify phase formalizes into `spec.md`.
4
+
5
+ Chat command: **`/elicit`**. CLI scaffold: `req-analysis init`.
6
+
7
+ ## When to Use
8
+
9
+ **Project mode** (`--scope project`):
10
+
11
+ - After `install`, when a kickoff brief exists (file or pasted into chat)
12
+ - Brownfield after `project-init` — enrich PROJECT + ROADMAP before first feature
13
+ - Owner asks to "analyze the project" or "read my PRD and ask questions"
14
+
15
+ **Feature mode** (`--scope feature`):
16
+
17
+ - A specific delivery is named but details are missing ("add a settings page", "improve login")
18
+ - A ROADMAP candidate needs detail before `/specify`
19
+ - `/specify` would otherwise need many `[NEEDS CLARIFICATION]` markers
20
+
21
+ **Suggest, never block.** If the owner prefers `/specify` directly, proceed — Elicitation is optional.
22
+
23
+ ## When NOT to Use
24
+
25
+ - **Quick tier** — use `quick-mode.md`
26
+ - **Explore** — owner still choosing *what* to build (options A/B/C), not *how*
27
+ - **Spec-ready request** — testable goal, clear scope → `/specify` directly
28
+ - **Discuss** — gray irreversible decision *inside* an existing spec draft → `discuss.md`
29
+
30
+ ## Phase boundaries (do not repeat other phases)
31
+
32
+ | Phase | Asks about | Output |
33
+ | --- | --- | --- |
34
+ | **Explore** | What to build — alternatives | none |
35
+ | **Elicit (project)** | Gaps in kickoff + macro scope | `.specs/project/requirements-brief.md` |
36
+ | **Elicit (feature)** | Gaps for one delivery | `.specs/project/feature-briefs/[slug]/requirements-brief.md` |
37
+ | **Specify** | Formal REQ/EARS — minimal questions | `spec.md` |
38
+ | **Discuss** | Irreversible gray areas mid-spec | `context.md` |
39
+
40
+ Before each question: **cite the source** — "kickoff §Goal already says X — confirm?" or "not found — A) … B) …".
41
+
42
+ ## Inputs
43
+
44
+ - Kickoff brief — any of:
45
+ - `.specs/project/kickoff.md` (pasted chat or saved export)
46
+ - `prd.md`, `docs/brief.md`, `docs/prd.md` (discovered automatically)
47
+ - Owner paste in chat → persist to `.specs/project/kickoff.md` on first turn
48
+ - `.specs/project/PROJECT.md`, `ROADMAP.md` when present
49
+ - Relevant code (`code-index search`, targeted reads) for brownfield
50
+ - `memory-retrieve` after `memory-index rebuild` for prior briefs
51
+ - `context-limits.md` — one scope at a time
52
+
53
+ **Not in v1:** dedicated GitHub or ChatPRD CLI. If the owner has external docs, they paste export or point to a local path.
54
+
55
+ ## Output
56
+
57
+ | Scope | Primary artifact | Also updates |
58
+ | --- | --- | --- |
59
+ | **project** | `.specs/project/requirements-brief.md` | ROADMAP feature candidates (suggested slugs) |
60
+ | **feature** | `.specs/project/feature-briefs/[slug]/requirements-brief.md` | — |
61
+
62
+ Optional scaffold:
63
+
64
+ ```bash
65
+ npx @luizsantiago/spec-guardrails req-analysis init "description" --scope project
66
+ npx @luizsantiago/spec-guardrails req-analysis init "settings page" --scope feature
67
+ ```
68
+
69
+ ## Procedure
70
+
71
+ ### 0. Choose scope
72
+
73
+ | Signal | Scope |
74
+ | --- | --- |
75
+ | First project pass, kickoff/PRD present, no project brief yet | **project** |
76
+ | Named feature, vague details, or ROADMAP item | **feature** |
77
+ | Owner unsure | Ask once; default **feature** for a single ask, **project** for greenfield kickoff |
78
+
79
+ Run `req-analysis init` when templates help; otherwise create the brief path directly.
80
+
81
+ ### 1. Gather context (read-only)
82
+
83
+ 1. List **Context sources** — every file read (paths + section if partial).
84
+ 2. Run `req-analysis discover` (or read kickoff paths manually): `prd.md`, `docs/brief.md`, `.specs/project/kickoff.md`.
85
+ 3. Skim PROJECT.md, ROADMAP, brownfield code if relevant.
86
+ 4. `code-index search "<keywords>"` when the repo already has code.
87
+ 5. `memory-retrieve "<topic>"` when memory index exists.
88
+
89
+ Summarize **Current state** in the brief — do not copy entire kickoff verbatim.
90
+
91
+ ### 2. Gap analysis
92
+
93
+ List gaps only — requirements the kickoff + repo **do not** answer:
94
+
95
+ - UI: screens, buttons, actions, empty/loading/error
96
+ - API: endpoints, auth, errors
97
+ - Data: entities, retention
98
+ - Scope: in / out for this pass
99
+
100
+ Mark covered items: `covered by kickoff.md §…` — **do not ask again**.
101
+
102
+ ### 3. Elicitation rounds
103
+
104
+ | Rule | Value |
105
+ | --- | --- |
106
+ | Max questions per round | **5** |
107
+ | Format | Concrete options A/B/C + recommendation (same as `discuss.md`) |
108
+ | One area per round | UI **or** API **or** data — not all at once |
109
+ | Stop when | Gaps closed, owner says "enough", or Open questions is `- none` |
110
+
111
+ Templates by detected type (ask only relevant dimensions):
112
+
113
+ - **UI** — layout, primary actions, navigation, states
114
+ - **API** — contract, auth, errors, idempotency
115
+ - **Data** — entities, migrations, retention
116
+ - **CLI/infra** — flags, envs, rollback
117
+
118
+ No production code. No `spec.md` until `/specify`.
119
+
120
+ ### 4. Write the brief
121
+
122
+ Use the template from `req-analysis init`. Required sections:
123
+
124
+ - **Goal** — one sentence
125
+ - **Context sources**
126
+ - **Current state**
127
+ - **Capabilities** — product language, not EARS
128
+ - **Interaction details** — when UI/API applies
129
+ - **Constraints & out of scope**
130
+ - **Resolved questions** — D-001 format (from `discuss.md`)
131
+ - **Open questions** — `- none` when done
132
+ - **Feature candidates** (project mode) — suggested NNN-slug + one-line goal
133
+ - **Owner approval** — date + explicit yes
134
+
135
+ ### 5. Owner approval
136
+
137
+ Present the brief summary. Wait for explicit approval before `/specify`.
138
+
139
+ **v1 checklist (no Python gate yet):**
140
+
141
+ - [ ] Open questions is `- none` or empty
142
+ - [ ] Owner approval filled
143
+ - [ ] Context sources lists at least one input
144
+ - [ ] No `[NEEDS CLARIFICATION]` or `[OPEN QUESTION]` left
145
+
146
+ ### 6. Transition
147
+
148
+ **Project mode:**
149
+
150
+ 1. Update ROADMAP with feature candidates (stubs — no spec.md yet).
151
+ 2. Owner picks next feature → **feature mode** if still vague, else `/specify`.
152
+
153
+ **Feature mode:**
154
+
155
+ ```bash
156
+ npx @luizsantiago/spec-guardrails feature-init "owner description"
157
+ ```
158
+
159
+ Copy or link brief → `.specs/features/NNN-slug/requirements-brief.md` (optional).
160
+
161
+ Open `specify.md` — derive `spec.md` from brief; do **not** re-ask resolved questions.
162
+
163
+ ```bash
164
+ npx @luizsantiago/spec-guardrails req-analysis promote --scope feature
165
+ ```
166
+
167
+ Prints promote steps when unsure.
168
+
169
+ ## Router (hub — suggest only)
170
+
171
+ When the owner message is vague (interface without flow, "improve X" without criteria, kickoff exists but no `requirements-brief.md`):
172
+
173
+ > Suggest: "Want `/elicit` (a few targeted questions) or go straight to `/specify`?"
174
+
175
+ Never refuse `/specify`. Never run Elicitation without owner consent when they chose Specify.
176
+
177
+ ## Rules
178
+
179
+ - English artifacts (see `engineering-standards.md`).
180
+ - Complement kickoff — do not rewrite or replace the owner's product brief.
181
+ - Lazy artifacts — create brief only when Elicitation runs.
182
+ - Re-run `memory-index rebuild` after approving a project brief so kickoff + brief are searchable.
183
+
184
+ ## Anti-Patterns
185
+
186
+ | Avoid | Prefer |
187
+ | --- | --- |
188
+ | 80 generic questions | ≤5 per round until gaps close |
189
+ | Re-asking kickoff content | Cite source + confirm |
190
+ | Writing spec.md here | Brief in product language; Specify formalizes |
191
+ | Duplicating Discuss | Irreversible grays wait for Discuss inside Specify |
192
+
193
+ ## Next
194
+
195
+ - Brief approved, feature chosen → `specify.md`
196
+ - Still choosing what to build → `explore.md`
197
+ - Back → `agent-architecture.md`
@@ -57,5 +57,6 @@ None required. Optionally capture decisions in chat. When the idea crystallizes,
57
57
  ## Next
58
58
 
59
59
  - Scope is clear → `feature-init` then `specify.md`
60
+ - Details missing but direction chosen → `elicitation.md` (feature scope)
60
61
  - Gray areas remain → `discuss.md` inside Specify
61
62
  - Back → `agent-architecture.md`
@@ -14,6 +14,7 @@ Capture WHAT to build as testable, traceable requirements. Always required (exce
14
14
  ## Inputs
15
15
 
16
16
  - Owner's request, in their own words
17
+ - `.specs/project/requirements-brief.md` or `.specs/project/feature-briefs/*/requirements-brief.md` when `/elicit` ran — do not re-ask resolved questions
17
18
  - `.specs/STATE.md` decisions (`AD-NNN`) relevant to this area
18
19
  - `.specs/project/CONSTITUTION.md` when present
19
20
  - `.specs/LESSONS.md` entries that apply — load them with `python3 .specs/guardrails/scripts/lessons.py list --status confirmed`
@@ -63,6 +63,15 @@ memory:
63
63
  semantic_weight: 0.4
64
64
  graph_depth: 1
65
65
 
66
+ # Elicitation (/elicit — optional, before Specify)
67
+ elicitation:
68
+ max_questions_per_turn: 5
69
+ kickoff_discovery_paths:
70
+ - prd.md
71
+ - docs/brief.md
72
+ - docs/prd.md
73
+ - .specs/project/kickoff.md
74
+
66
75
  # Soft OS sandbox (optional — policy, not containers)
67
76
  sandbox:
68
77
  mode: warn