@luizsantiago/spec-guardrails 4.5.0 → 4.5.2
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 +6 -4
- package/index.js +1 -4
- package/lib/gates.js +12 -0
- package/lib/install.js +1 -1
- package/package.json +1 -1
- package/scripts/_project_config.py +10 -10
- package/scripts/analyze_artifacts.py +2 -0
- package/scripts/check_commit.py +8 -5
- package/scripts/check_suppressions.py +8 -10
- package/scripts/run_quality_checks.py +5 -2
- package/skills/agent-architecture.md +9 -7
- package/templates/config.yaml.example +6 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Spec Guardrails
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@luizsantiago/spec-guardrails)
|
|
4
|
-
[](https://www.npmjs.com/package/@luizsantiago/spec-guardrails)
|
|
5
4
|
[](https://github.com/luizssantiago92/spec-guardrails/actions/workflows/ci.yml)
|
|
6
5
|
[](LICENSE)
|
|
7
6
|
|
|
@@ -39,7 +38,9 @@ npx @luizsantiago/spec-guardrails install
|
|
|
39
38
|
npx @luizsantiago/spec-guardrails doctor
|
|
40
39
|
```
|
|
41
40
|
|
|
42
|
-
`install` writes the phase guides for your agent and creates the `.specs/` folder.
|
|
41
|
+
`install` writes the phase guides for your agent and creates the `.specs/` folder. By default it detects your platform (Cursor, Claude Code, Copilot, or Codex) and installs **one** skill tree plus that platform's adapter entry file. Existing trees are preserved when you switch IDEs. Use `install --all-platforms` for every tree, or `install --platform <id>` to force one.
|
|
42
|
+
|
|
43
|
+
Re-run `install` after upgrading the package — your existing `.specs/` notes are preserved. After that, you work in **agent chat**, not in the terminal; the agent calls the CLI and checks when needed.
|
|
43
44
|
|
|
44
45
|
| Requirement | Role |
|
|
45
46
|
| --- | --- |
|
|
@@ -139,12 +140,12 @@ Automatic checks at step boundaries — each one blocks a specific kind of short
|
|
|
139
140
|
| `validate-traceability` | REQ → task → proof chain is broken |
|
|
140
141
|
| `validate-state` | Feature is declared done without evidence |
|
|
141
142
|
| `validate-quick` | Quick-mode fix broke its size or shape rules |
|
|
142
|
-
| `check-commit` | Commit message
|
|
143
|
+
| `check-commit` | Commit message is not conventional, staged diff is empty, or exceeds `commit.max_staged_lines` |
|
|
143
144
|
| `check-suppressions` | Staged diff adds `# noqa`, `eslint-disable`, `@ts-ignore`, skipped tests, or `--no-verify` |
|
|
144
145
|
| `quality-checks` | Configured project commands (`npm test`, …) fail during `/verify` |
|
|
145
146
|
| `lessons` | A failed verify tries to skip the lesson step |
|
|
146
147
|
|
|
147
|
-
→ [Gates](docs/guide/gates.md) · [
|
|
148
|
+
→ [Gates](docs/guide/gates.md) · [Guarantees matrix](docs/guide/Guarantees-matrix.md)
|
|
148
149
|
|
|
149
150
|
### Requirements analysis
|
|
150
151
|
|
|
@@ -235,6 +236,7 @@ Spec Guardrails adapts patterns from open-source work. These are the projects wh
|
|
|
235
236
|
| [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) | MIT | Design-discussion patterns and definition-of-done framing |
|
|
236
237
|
| [graph-engineering](https://github.com/codejunkie99/graph-engineering) | MIT | Task-graph rules behind safe parallel waves |
|
|
237
238
|
| [loop-engineering](https://github.com/cobusgreyling/loop-engineering) | MIT | Wave-based execution model |
|
|
239
|
+
| [loopgate_harness](https://github.com/rxdt/loopgate_harness) | MIT | Suppression-bypass blocking, project-configured quality commands as verify evidence, honest-limits framing, and README diagram layout (proxy-safe SVG; SMIL animation on GitHub) |
|
|
238
240
|
|
|
239
241
|
Everything else — the CLI, the Python checks, the platform adapters, and the requirements-analysis phase — is original work in this repository. Full lineage, including references we cite but do not bundle: [Credits and lineage](docs/guide/credits.md).
|
|
240
242
|
|
package/index.js
CHANGED
|
@@ -101,7 +101,7 @@ Commands:
|
|
|
101
101
|
[--no-roadmap] Skip ROADMAP update
|
|
102
102
|
[--no-domain] Skip domain spec merge
|
|
103
103
|
[--no-state] Skip STATE reset
|
|
104
|
-
classify-change
|
|
104
|
+
classify-change <desc> [files...] Heuristic complexity tier (quick/simple/medium/complex)
|
|
105
105
|
[--json] Machine-readable output
|
|
106
106
|
feature-status [feature] Artifact checklist + next step for a feature
|
|
107
107
|
[--json] Machine-readable output
|
|
@@ -872,9 +872,6 @@ if (command === "--version" || command === "-v" || command === "version") {
|
|
|
872
872
|
process.stdout.write(formatExplorationStatus(status, { json }));
|
|
873
873
|
} else if (sub === "validate") {
|
|
874
874
|
const featureId = rest[0];
|
|
875
|
-
if (!featureId) {
|
|
876
|
-
throw new Error("Usage: solution-explore validate <feature>");
|
|
877
|
-
}
|
|
878
875
|
const result = await validateExplorationArtifact(cwd, featureId);
|
|
879
876
|
process.stdout.write(formatExplorationValidation(result, { json }));
|
|
880
877
|
if (!result.ok) {
|
package/lib/gates.js
CHANGED
|
@@ -229,6 +229,18 @@ export async function runGuardrailsScriptCapture(command, args, options = {}) {
|
|
|
229
229
|
const cwd = options.cwd ?? process.cwd();
|
|
230
230
|
const scriptsDir = await resolveScriptsDir(cwd);
|
|
231
231
|
const scriptPath = path.join(cwd, scriptsDir, scriptName);
|
|
232
|
+
|
|
233
|
+
for (const required of [scriptName, "_common.py"]) {
|
|
234
|
+
try {
|
|
235
|
+
await access(path.join(cwd, scriptsDir, required), constants.R_OK);
|
|
236
|
+
} catch {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`Guardrails script not found at ${path.join(scriptsDir, required)}. ` +
|
|
239
|
+
`Run \`${NPX("install")}\` in this project first.`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
232
244
|
const python = await resolvePython();
|
|
233
245
|
|
|
234
246
|
if (!python) {
|
package/lib/install.js
CHANGED
|
@@ -167,7 +167,7 @@ export async function install(options = {}) {
|
|
|
167
167
|
const pythonAvailable = await hasPython();
|
|
168
168
|
if (!pythonAvailable) {
|
|
169
169
|
log(
|
|
170
|
-
"⚠️ Python 3 not found — Process mode only (flexible workflow, manual checkpoints). " +
|
|
170
|
+
"⚠️ Python 3.10+ not found — Process mode only (flexible workflow, manual checkpoints). " +
|
|
171
171
|
"Install Python 3.10+ for Brakes mode (full kit with automatic gates).",
|
|
172
172
|
);
|
|
173
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luizsantiago/spec-guardrails",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Governed spec-driven development for AI coding agents. Your agent writes the spec, gets your approval, builds in small waves, and proves the result — plans and project memory stored as files in your repo.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import re
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
CONFIG_RELATIVE = Path(".specs") / "config.yaml"
|
|
9
9
|
|
|
10
10
|
DEFAULT_SUPPRESSION_PATTERNS = [
|
|
11
11
|
r"#\s*noqa\b",
|
|
@@ -22,6 +22,11 @@ DEFAULT_SUPPRESSION_PATTERNS = [
|
|
|
22
22
|
DEFAULT_MAX_STAGED_LINES = 500
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def config_path(cwd: Path | str | None = None) -> Path:
|
|
26
|
+
root = Path(cwd) if cwd is not None else Path.cwd()
|
|
27
|
+
return root / CONFIG_RELATIVE
|
|
28
|
+
|
|
29
|
+
|
|
25
30
|
def _parse_scalar(raw: str):
|
|
26
31
|
value = raw.strip().strip("'\"")
|
|
27
32
|
lower = value.lower()
|
|
@@ -52,7 +57,7 @@ def _parse_list_block(lines: list[str], start_index: int, parent_indent: int) ->
|
|
|
52
57
|
return items, index
|
|
53
58
|
|
|
54
59
|
|
|
55
|
-
def load_project_config() -> dict:
|
|
60
|
+
def load_project_config(cwd: Path | str | None = None) -> dict:
|
|
56
61
|
"""Return quality, suppressions, and commit policy blocks with defaults."""
|
|
57
62
|
|
|
58
63
|
config = {
|
|
@@ -61,14 +66,13 @@ def load_project_config() -> dict:
|
|
|
61
66
|
"commit": {"max_staged_lines": DEFAULT_MAX_STAGED_LINES},
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
path = config_path(cwd)
|
|
70
|
+
if not path.is_file():
|
|
65
71
|
return config
|
|
66
72
|
|
|
67
|
-
lines =
|
|
73
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
68
74
|
section = None
|
|
69
75
|
section_indent = 0
|
|
70
|
-
subsection = None
|
|
71
|
-
subsection_indent = 0
|
|
72
76
|
|
|
73
77
|
index = 0
|
|
74
78
|
while index < len(lines):
|
|
@@ -83,27 +87,23 @@ def load_project_config() -> dict:
|
|
|
83
87
|
if stripped == "quality:":
|
|
84
88
|
section = "quality"
|
|
85
89
|
section_indent = indent
|
|
86
|
-
subsection = None
|
|
87
90
|
index += 1
|
|
88
91
|
continue
|
|
89
92
|
|
|
90
93
|
if stripped == "suppressions:":
|
|
91
94
|
section = "suppressions"
|
|
92
95
|
section_indent = indent
|
|
93
|
-
subsection = None
|
|
94
96
|
index += 1
|
|
95
97
|
continue
|
|
96
98
|
|
|
97
99
|
if stripped == "commit:":
|
|
98
100
|
section = "commit"
|
|
99
101
|
section_indent = indent
|
|
100
|
-
subsection = None
|
|
101
102
|
index += 1
|
|
102
103
|
continue
|
|
103
104
|
|
|
104
105
|
if section and indent <= section_indent and not stripped.endswith(":"):
|
|
105
106
|
section = None
|
|
106
|
-
subsection = None
|
|
107
107
|
|
|
108
108
|
if section == "quality" and stripped == "checks:":
|
|
109
109
|
items, index = _parse_list_block(lines, index + 1, indent)
|
package/scripts/check_commit.py
CHANGED
|
@@ -29,7 +29,7 @@ import sys
|
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
|
|
31
31
|
from _common import EXIT_OK, EXIT_USAGE, Report
|
|
32
|
-
from _project_config import load_project_config
|
|
32
|
+
from _project_config import DEFAULT_MAX_STAGED_LINES, load_project_config
|
|
33
33
|
|
|
34
34
|
GATE = "check-commit"
|
|
35
35
|
|
|
@@ -59,11 +59,14 @@ def read_staged_diff(cwd: Path) -> str:
|
|
|
59
59
|
cwd=cwd,
|
|
60
60
|
capture_output=True,
|
|
61
61
|
text=True,
|
|
62
|
+
# Paths can carry bytes the platform locale cannot decode.
|
|
63
|
+
encoding="utf-8",
|
|
64
|
+
errors="replace",
|
|
62
65
|
check=False,
|
|
63
66
|
)
|
|
64
67
|
if result.returncode not in (0, 1):
|
|
65
|
-
raise RuntimeError(result.stderr.strip() or "git diff --cached --numstat failed")
|
|
66
|
-
return result.stdout
|
|
68
|
+
raise RuntimeError((result.stderr or "").strip() or "git diff --cached --numstat failed")
|
|
69
|
+
return result.stdout or ""
|
|
67
70
|
|
|
68
71
|
|
|
69
72
|
def count_staged_lines(numstat_text: str) -> int:
|
|
@@ -81,8 +84,8 @@ def count_staged_lines(numstat_text: str) -> int:
|
|
|
81
84
|
|
|
82
85
|
def build_staged_report(cwd: Path) -> Report:
|
|
83
86
|
report = Report(gate=GATE, target="staged changes")
|
|
84
|
-
config = load_project_config()
|
|
85
|
-
max_lines = int(config.get("commit", {}).get("max_staged_lines") or
|
|
87
|
+
config = load_project_config(cwd)
|
|
88
|
+
max_lines = int(config.get("commit", {}).get("max_staged_lines") or DEFAULT_MAX_STAGED_LINES)
|
|
86
89
|
|
|
87
90
|
if not (cwd / ".git").exists():
|
|
88
91
|
report.error("not a git repository")
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Run before commit when agents might silence linters or skip hooks:
|
|
5
5
|
|
|
6
6
|
python3 check_suppressions.py
|
|
7
|
-
python3 check_suppressions.py --
|
|
7
|
+
python3 check_suppressions.py --cwd /path/to/repo
|
|
8
8
|
|
|
9
9
|
Scans `git diff --cached` for patterns such as `# noqa`, `eslint-disable`,
|
|
10
10
|
`@ts-ignore`, skipped tests, or `--no-verify`. Patterns are configurable under
|
|
@@ -33,11 +33,14 @@ def read_staged_diff(cwd: Path) -> str:
|
|
|
33
33
|
cwd=cwd,
|
|
34
34
|
capture_output=True,
|
|
35
35
|
text=True,
|
|
36
|
+
# Diffs can carry bytes the platform locale cannot decode (SVG, em dashes).
|
|
37
|
+
encoding="utf-8",
|
|
38
|
+
errors="replace",
|
|
36
39
|
check=False,
|
|
37
40
|
)
|
|
38
41
|
if result.returncode not in (0, 1):
|
|
39
|
-
raise RuntimeError(result.stderr.strip() or "git diff --cached failed")
|
|
40
|
-
return result.stdout
|
|
42
|
+
raise RuntimeError((result.stderr or "").strip() or "git diff --cached failed")
|
|
43
|
+
return result.stdout or ""
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
def added_lines(diff_text: str) -> list[tuple[str, str]]:
|
|
@@ -83,11 +86,6 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
83
86
|
default=".",
|
|
84
87
|
help="repository root (default: current directory)",
|
|
85
88
|
)
|
|
86
|
-
parser.add_argument(
|
|
87
|
-
"--strict",
|
|
88
|
-
action="store_true",
|
|
89
|
-
help="treat warnings as blocking failures",
|
|
90
|
-
)
|
|
91
89
|
args = parser.parse_args(argv)
|
|
92
90
|
|
|
93
91
|
cwd = Path(args.cwd).resolve()
|
|
@@ -96,7 +94,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
96
94
|
print(" error not a git repository")
|
|
97
95
|
return EXIT_USAGE
|
|
98
96
|
|
|
99
|
-
config = load_project_config()
|
|
97
|
+
config = load_project_config(cwd)
|
|
100
98
|
patterns = config.get("suppressions", {}).get("patterns") or DEFAULT_SUPPRESSION_PATTERNS
|
|
101
99
|
|
|
102
100
|
try:
|
|
@@ -107,7 +105,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
107
105
|
return EXIT_USAGE
|
|
108
106
|
|
|
109
107
|
report = build_report(diff_text, patterns)
|
|
110
|
-
return report.emit(
|
|
108
|
+
return report.emit()
|
|
111
109
|
|
|
112
110
|
|
|
113
111
|
if __name__ == "__main__":
|
|
@@ -14,7 +14,7 @@ Configure under:
|
|
|
14
14
|
- npm test
|
|
15
15
|
- npm run lint
|
|
16
16
|
|
|
17
|
-
Exit codes: 0 all passed, 1 one or more failed
|
|
17
|
+
Exit codes: 0 all passed or skipped (no checks configured), 1 one or more failed.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
@@ -38,6 +38,9 @@ def run_command(command: str, cwd: Path) -> dict:
|
|
|
38
38
|
shell=True,
|
|
39
39
|
capture_output=True,
|
|
40
40
|
text=True,
|
|
41
|
+
# Test runners emit symbols the platform locale cannot always decode.
|
|
42
|
+
encoding="utf-8",
|
|
43
|
+
errors="replace",
|
|
41
44
|
check=False,
|
|
42
45
|
)
|
|
43
46
|
output = (completed.stdout or "") + (completed.stderr or "")
|
|
@@ -58,7 +61,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
58
61
|
args = parser.parse_args(argv)
|
|
59
62
|
|
|
60
63
|
cwd = Path(args.cwd).resolve()
|
|
61
|
-
checks = load_project_config().get("quality", {}).get("checks") or []
|
|
64
|
+
checks = load_project_config(cwd).get("quality", {}).get("checks") or []
|
|
62
65
|
|
|
63
66
|
if not checks:
|
|
64
67
|
message = {
|
|
@@ -59,7 +59,9 @@ Structural gates run **before** owner review, so they cannot drift when the mode
|
|
|
59
59
|
| Retrieve related context | `npx @luizsantiago/spec-guardrails memory-retrieve "<query>"` |
|
|
60
60
|
| Rebuild / embed memory index | `npx @luizsantiago/spec-guardrails memory-index rebuild` · `memory-index embed` |
|
|
61
61
|
| On gate retry (Execute playbook) | `npx @luizsantiago/spec-guardrails execution-policy record-retry Tn` |
|
|
62
|
-
| On each commit | `python3 .specs/guardrails/scripts/check_commit.py --message "<message>"` |
|
|
62
|
+
| On each commit | `python3 .specs/guardrails/scripts/check_commit.py --message "<message>"` · `check_commit.py --staged` |
|
|
63
|
+
| Before each commit (staged diff) | `python3 .specs/guardrails/scripts/check_suppressions.py` |
|
|
64
|
+
| During Verify, when `quality.checks` is configured | `python3 .specs/guardrails/scripts/run_quality_checks.py` |
|
|
63
65
|
| Before declaring a feature done | `python3 .specs/guardrails/scripts/validate_state.py [feature]` |
|
|
64
66
|
| Traceability (Medium+ features) | `python3 .specs/guardrails/scripts/validate_traceability.py [feature]` |
|
|
65
67
|
| Quick mode evidence | `python3 .specs/guardrails/scripts/validate_quick.py [feature]` |
|
|
@@ -82,19 +84,19 @@ EXPLORE (optional) → ELICIT (optional) → SPECIFY → DISCUSS (conditional)
|
|
|
82
84
|
| Phase | Required | Reference | Sister skill | Gate |
|
|
83
85
|
| --- | --- | --- | --- | --- |
|
|
84
86
|
| **Explore** | Optional | `references/explore.md` | — | — |
|
|
85
|
-
| **Elicit** | Optional | `references/elicitation.md` |
|
|
87
|
+
| **Elicit** | Optional | `references/elicitation.md` | — | `validate_req_analysis.py` (before `/specify`; suggest-only entry) |
|
|
86
88
|
| **Constitution** | Once per project | `references/constitution.md` | — | — |
|
|
87
89
|
| **Specify** | Yes | `references/specify.md` | — | `validate_spec.py` |
|
|
88
90
|
| **Discuss** | Conditional | `references/discuss.md` | — | — |
|
|
89
91
|
| **Design** | No | `references/design.md` | — | — |
|
|
90
92
|
| **Tasks** | No | `references/tasks.md` | `task-graph-engineering.md` | `validate_tasks.py` |
|
|
91
93
|
| **Analyze** | Before task approval | `references/analyze.md` | — | `analyze_artifacts.py` |
|
|
92
|
-
| **Execute** | Yes | `references/implement.md` | `engineering-standards.md` | `check_commit.py` |
|
|
93
|
-
| **Verify** | Yes | `references/validate.md` | `security-review.md` | `validate_state.py` |
|
|
94
|
+
| **Execute** | Yes | `references/implement.md` | `engineering-standards.md` | `check_commit.py`, `check_suppressions.py` |
|
|
95
|
+
| **Verify** | Yes | `references/validate.md` | `security-review.md` | `validate_traceability.py`, `validate_state.py`, `run_quality_checks.py` |
|
|
94
96
|
| **Archive** | After Verify PASS | `references/archive.md` | `git-handoff.md` | `archive-feature` |
|
|
95
97
|
| **Converge** | On drift | `references/converge.md` | — | `analyze_artifacts.py` |
|
|
96
98
|
| **Handoff** | Yes | `references/memory.md` | `git-handoff.md` | — |
|
|
97
|
-
| **Quick** | Alternative | `references/quick-mode.md` | — | `check_commit.py`, `validate_quick.py` |
|
|
99
|
+
| **Quick** | Alternative | `references/quick-mode.md` | — | `check_commit.py`, `check_suppressions.py`, `validate_quick.py` |
|
|
98
100
|
| **Context** | Always | `references/context-limits.md` | — | — |
|
|
99
101
|
| **Sub-agents** | When batched | `references/sub-agents.md` | `task-graph-engineering.md` | — |
|
|
100
102
|
| **Solution exploration** | Explicit fork | `references/solution-exploration.md` | — | `solution-explore validate` |
|
|
@@ -119,7 +121,7 @@ Complexity determines depth. Do not run every phase on every change.
|
|
|
119
121
|
|
|
120
122
|
| Tier | Scope | Path |
|
|
121
123
|
| --- | --- | --- |
|
|
122
|
-
| **Quick** | ≤3 files, no design decisions, no new dependencies | `references/quick-mode.md` — describe, implement, verify, commit; gates: `check_commit.py`
|
|
124
|
+
| **Quick** | ≤3 files, no design decisions, no new dependencies | `references/quick-mode.md` — describe, implement, verify, commit; gates: `check_commit.py`, `check_suppressions.py`, `validate-quick` |
|
|
123
125
|
| **Simple** | 2–5 files, localized change | Specify → Execute → Verify |
|
|
124
126
|
| **Medium** | New feature, <10 tasks | Specify → Tasks → Execute → Verify |
|
|
125
127
|
| **Complex** | New architecture, API surface, infra | Specify → Discuss → Design → Tasks → Execute → Verify |
|
|
@@ -129,7 +131,7 @@ Complexity determines depth. Do not run every phase on every change.
|
|
|
129
131
|
|
|
130
132
|
**Rules**
|
|
131
133
|
|
|
132
|
-
- **Specify and Verify are always required on the full pipeline** — you must know WHAT was asked and prove it was delivered. **Quick** is the exception: the express lane in `references/quick-mode.md` (describe → implement → verify → commit) with `check_commit.py` on each commit and `validate-quick` as the close/evidence gate.
|
|
134
|
+
- **Specify and Verify are always required on the full pipeline** — you must know WHAT was asked and prove it was delivered. **Quick** is the exception: the express lane in `references/quick-mode.md` (describe → implement → verify → commit) with `check_commit.py`, `check_suppressions.py` on each commit, and `validate-quick` as the close/evidence gate.
|
|
133
135
|
- **Design is skipped** when there are no architectural decisions and no new patterns.
|
|
134
136
|
- **Tasks is skipped** when there are ≤3 obvious steps.
|
|
135
137
|
- **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.
|
|
@@ -83,12 +83,17 @@ sandbox:
|
|
|
83
83
|
- "\\b(DROP\\s+DATABASE|DROP\\s+SCHEMA|TRUNCATE\\s+TABLE)\\b"
|
|
84
84
|
|
|
85
85
|
# Verification suppressions (optional — block agents from silencing linters/tests)
|
|
86
|
+
# Omit this block to keep runtime defaults from _project_config.py (9 patterns).
|
|
86
87
|
suppressions:
|
|
87
88
|
patterns:
|
|
88
89
|
- "#\\s*noqa\\b"
|
|
89
|
-
- "
|
|
90
|
+
- "nosemgrep\\b"
|
|
91
|
+
- "eslint-disable(?:-next-line|-line)?"
|
|
90
92
|
- "@ts-ignore\\b"
|
|
93
|
+
- "@ts-expect-error\\b"
|
|
91
94
|
- "\\bxit\\s*\\("
|
|
95
|
+
- "\\bxdescribe\\s*\\("
|
|
96
|
+
- "\\bpytest\\.mark\\.skip\\b"
|
|
92
97
|
- "--no-verify\\b"
|
|
93
98
|
|
|
94
99
|
# Project quality commands (optional — run during /verify)
|