@luizsantiago/spec-guardrails 3.0.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/index.js +335 -0
  4. package/lib/archive.js +208 -0
  5. package/lib/assets.js +145 -0
  6. package/lib/brownfield.js +446 -0
  7. package/lib/config.js +293 -0
  8. package/lib/constants.js +262 -0
  9. package/lib/cursorrules.js +92 -0
  10. package/lib/delta-merge.js +248 -0
  11. package/lib/doctor.js +343 -0
  12. package/lib/download.js +133 -0
  13. package/lib/feature.js +272 -0
  14. package/lib/fs-utils.js +114 -0
  15. package/lib/gates.js +138 -0
  16. package/lib/install.js +140 -0
  17. package/lib/memory.js +34 -0
  18. package/lib/next-steps.js +50 -0
  19. package/lib/presets.js +176 -0
  20. package/lib/project-rules.js +210 -0
  21. package/lib/specs-utils.js +117 -0
  22. package/lib/token-cost.js +124 -0
  23. package/package.json +46 -0
  24. package/rules/engineering-baseline.mdc +56 -0
  25. package/scripts/_common.py +356 -0
  26. package/scripts/analyze_artifacts.py +187 -0
  27. package/scripts/check_commit.py +140 -0
  28. package/scripts/lessons.py +447 -0
  29. package/scripts/loop_plan.py +217 -0
  30. package/scripts/validate_spec.py +345 -0
  31. package/scripts/validate_state.py +385 -0
  32. package/scripts/validate_tasks.py +379 -0
  33. package/skills/agent-architecture.md +221 -0
  34. package/skills/appsec.md +83 -0
  35. package/skills/code-simplify.md +49 -0
  36. package/skills/engineering-standards.md +98 -0
  37. package/skills/git-handoff.md +213 -0
  38. package/skills/qa-strategy.md +83 -0
  39. package/skills/references/analyze.md +56 -0
  40. package/skills/references/archive.md +60 -0
  41. package/skills/references/constitution.md +66 -0
  42. package/skills/references/context-limits.md +73 -0
  43. package/skills/references/converge.md +47 -0
  44. package/skills/references/design.md +88 -0
  45. package/skills/references/discuss.md +68 -0
  46. package/skills/references/explore.md +61 -0
  47. package/skills/references/implement.md +175 -0
  48. package/skills/references/lessons.md +71 -0
  49. package/skills/references/memory.md +98 -0
  50. package/skills/references/project-init.md +62 -0
  51. package/skills/references/quick-mode.md +84 -0
  52. package/skills/references/specify.md +144 -0
  53. package/skills/references/sub-agents.md +117 -0
  54. package/skills/references/tasks.md +178 -0
  55. package/skills/references/validate.md +210 -0
  56. package/skills/security-review.md +120 -0
  57. package/skills/ship-ready.md +50 -0
  58. package/skills/task-graph-engineering.md +180 -0
  59. package/templates/GETTING_STARTED.md +61 -0
  60. package/templates/config.yaml.example +28 -0
  61. package/templates/presets/default.yaml +16 -0
  62. package/templates/presets/node-ts.yaml +22 -0
  63. package/templates/presets/python.yaml +22 -0
@@ -0,0 +1,379 @@
1
+ #!/usr/bin/env python3
2
+ """Granularity gate for `.specs/features/[feature]/tasks.md`.
3
+
4
+ Run before presenting a task breakdown for approval:
5
+
6
+ python3 validate_tasks.py .specs/features/auth/tasks.md
7
+ python3 validate_tasks.py auth
8
+ python3 validate_tasks.py # when the project has a single feature
9
+
10
+ Checks:
11
+ * at least one task with a well-formed ID (T1, T2, ...)
12
+ * every task carries Requirement, Files, Depends on, Tests, Gate and Done when
13
+ * Tests and Done when reject placeholder values (none, —, n/a)
14
+ * dependencies reference existing tasks, never forward, never self
15
+ * a task never depends on a task in a later `### Phase N` group
16
+ * dependency graph is acyclic
17
+ * every requirement ID in sibling spec.md is covered by at least one task
18
+ * independent tasks do not share Files paths
19
+ * 3+ tasks require sibling task-graph.md (when validating on disk)
20
+ * vague task titles are flagged as granularity smells
21
+
22
+ Exit codes: 0 pass, 1 blocking issues, 2 usage error.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import re
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ from _common import (
33
+ Report,
34
+ find_placeholders,
35
+ normalize_file_path,
36
+ requirement_ids,
37
+ resolve_artifact,
38
+ visible_markdown,
39
+ )
40
+
41
+ GATE = "validate-tasks"
42
+
43
+ TASK_HEADING = re.compile(
44
+ r"^#{2,6}\s*(?P<id>T\d{1,6})\s*[:\-–]?\s*(?P<title>.*)$",
45
+ re.MULTILINE | re.IGNORECASE,
46
+ )
47
+ FIELD = re.compile(
48
+ r"^\s*[-*]?\s*\*{0,2}(?P<key>[A-Za-z][A-Za-z ]+?)\*{0,2}\s*:\s*(?P<value>.+?)\s*$",
49
+ re.MULTILINE,
50
+ )
51
+ # Do not treat REQ-T100 as task T100: a hyphen glued to a letter is not a
52
+ # task-id boundary. Still matches T1, T12, and "see T3".
53
+ TASK_REF = re.compile(r"(?<![A-Za-z0-9-])T(\d{1,6})\b", re.IGNORECASE)
54
+ PHASE_HEADING = re.compile(
55
+ r"^#{1,6}\s*Phase\s+(?P<number>\d+)\b", re.MULTILINE | re.IGNORECASE
56
+ )
57
+ REQUIREMENT_REF = re.compile(r"\b[A-Z][A-Z0-9]{1,9}-\d{2,4}\b")
58
+ NONE_VALUES = {"-", "—", "–", "none", "n/a", "na", "nenhum", "nenhuma", "no"}
59
+
60
+ REQUIRED_FIELDS = ("requirement", "files", "depends on", "tests", "gate", "done when")
61
+ # Depends on: — remains valid (no deps). Files/Gate/Tests/Done when reject none/—.
62
+ PLACEHOLDER_FIELDS = frozenset({"done when", "tests", "gate", "files"})
63
+ VAGUE_TITLE_WORDS = {
64
+ "implement feature",
65
+ "create form",
66
+ "build ui",
67
+ "do backend",
68
+ "make it work",
69
+ "finish",
70
+ "misc",
71
+ "cleanup",
72
+ "refactor code",
73
+ }
74
+
75
+
76
+ def parse_fields(body: str) -> dict[str, str]:
77
+ fields: dict[str, str] = {}
78
+ for match in FIELD.finditer(body):
79
+ key = match.group("key").strip().lower()
80
+ value = match.group("value").strip()
81
+ fields.setdefault(key, value)
82
+ return fields
83
+
84
+
85
+ def split_tasks(text: str) -> list[tuple[str, str, str]]:
86
+ matches = list(TASK_HEADING.finditer(text))
87
+ tasks: list[tuple[str, str, str]] = []
88
+
89
+ for index, match in enumerate(matches):
90
+ start = match.end()
91
+ end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
92
+ tasks.append(
93
+ (match.group("id").upper(), match.group("title").strip(), text[start:end])
94
+ )
95
+
96
+ return tasks
97
+
98
+
99
+ def task_phases(text: str) -> dict[str, int]:
100
+ """Map each task ID to the phase it sits under, or 0 when phases are unused."""
101
+
102
+ marks = [
103
+ (match.start(), int(match.group("number")))
104
+ for match in PHASE_HEADING.finditer(text)
105
+ ]
106
+
107
+ if not marks:
108
+ return {}
109
+
110
+ phases: dict[str, int] = {}
111
+
112
+ for match in TASK_HEADING.finditer(text):
113
+ current = 0
114
+ for position, number in marks:
115
+ if position < match.start():
116
+ current = number
117
+ else:
118
+ break
119
+ phases[match.group("id").upper()] = current
120
+
121
+ return phases
122
+
123
+
124
+ def parse_dependencies(value: str) -> list[str]:
125
+ if not value or value.strip().lower() in NONE_VALUES:
126
+ return []
127
+ return [f"T{number}" for number in TASK_REF.findall(value)]
128
+
129
+
130
+ def parse_files(value: str) -> list[str]:
131
+ if not value or value.strip().lower() in NONE_VALUES:
132
+ return []
133
+ files: list[str] = []
134
+ for chunk in re.split(r"[,;\n]", value):
135
+ path = normalize_file_path(chunk)
136
+ if path and path.lower() not in NONE_VALUES:
137
+ files.append(path)
138
+ return files
139
+
140
+
141
+ def detect_cycle(graph: dict[str, list[str]]) -> list[str] | None:
142
+ """Iterative DFS so a long dependency chain cannot blow the call stack."""
143
+
144
+ visited: set[str] = set()
145
+
146
+ for root in graph:
147
+ if root in visited:
148
+ continue
149
+
150
+ path: list[str] = []
151
+ on_path: set[str] = set()
152
+ stack: list[tuple[str, bool]] = [(root, False)]
153
+
154
+ while stack:
155
+ node, expanded = stack.pop()
156
+
157
+ if expanded:
158
+ path.pop()
159
+ on_path.discard(node)
160
+ continue
161
+
162
+ if node in visited:
163
+ continue
164
+
165
+ if node in on_path:
166
+ return path[path.index(node):] + [node]
167
+
168
+ visited.add(node)
169
+ path.append(node)
170
+ on_path.add(node)
171
+ stack.append((node, True))
172
+
173
+ for neighbour in graph.get(node, []):
174
+ if neighbour in on_path:
175
+ return path[path.index(neighbour):] + [neighbour]
176
+ if neighbour not in visited:
177
+ stack.append((neighbour, False))
178
+
179
+ return None
180
+
181
+
182
+ def reachable(graph: dict[str, list[str]], start: str, goal: str) -> bool:
183
+ if start == goal:
184
+ return True
185
+ seen = {start}
186
+ stack = [start]
187
+ while stack:
188
+ node = stack.pop()
189
+ for neighbour in graph.get(node, []):
190
+ if neighbour == goal:
191
+ return True
192
+ if neighbour not in seen:
193
+ seen.add(neighbour)
194
+ stack.append(neighbour)
195
+ return False
196
+
197
+
198
+ def build_report(
199
+ target: str,
200
+ text: str,
201
+ *,
202
+ spec_text: str | None = None,
203
+ feature_dir: Path | None = None,
204
+ ) -> Report:
205
+ report = Report(gate=GATE, target=target)
206
+ visible = visible_markdown(text)
207
+ tasks = split_tasks(visible)
208
+
209
+ if not tasks:
210
+ report.error("no tasks found - use '### T1: Short imperative title'")
211
+ return report
212
+
213
+ report.ok(f"{len(tasks)} task(s) with well-formed IDs")
214
+
215
+ order: dict[str, int] = {}
216
+ graph: dict[str, list[str]] = {}
217
+ seen: set[str] = set()
218
+ covered_requirements: set[str] = set()
219
+ files_by_task: dict[str, list[str]] = {}
220
+
221
+ for position, (task_id, title, body) in enumerate(tasks):
222
+ if task_id in seen:
223
+ report.error(f"duplicate task ID: {task_id}")
224
+ seen.add(task_id)
225
+ order[task_id] = position
226
+
227
+ if not title:
228
+ report.error(f"{task_id}: heading has no title")
229
+ elif title.strip().lower() in VAGUE_TITLE_WORDS:
230
+ report.error(f"{task_id}: title is not atomic: '{title}'")
231
+ elif len(title.split()) < 3:
232
+ report.warn(f"{task_id}: title may be too coarse: '{title}'")
233
+
234
+ fields = parse_fields(body)
235
+
236
+ for required in REQUIRED_FIELDS:
237
+ value = fields.get(required, "")
238
+ missing = not value
239
+ if required in PLACEHOLDER_FIELDS and value.strip().lower() in NONE_VALUES:
240
+ missing = True
241
+ if required == "files" and value and not parse_files(value):
242
+ missing = True
243
+ if missing:
244
+ report.error(f"{task_id}: missing '{required.title()}' field")
245
+
246
+ requirement = fields.get("requirement", "")
247
+ if requirement and not REQUIREMENT_REF.search(requirement):
248
+ report.error(
249
+ f"{task_id}: Requirement '{requirement}' does not reference a spec ID"
250
+ )
251
+ covered_requirements.update(REQUIREMENT_REF.findall(requirement))
252
+
253
+ files_by_task[task_id] = parse_files(fields.get("files", ""))
254
+ graph[task_id] = parse_dependencies(fields.get("depends on", ""))
255
+
256
+ phases = task_phases(visible)
257
+ if phases:
258
+ report.ok(f"{len(set(phases.values()))} execution phase(s) detected")
259
+
260
+ for task_id, dependencies in graph.items():
261
+ for dependency in dependencies:
262
+ if dependency == task_id:
263
+ report.error(f"{task_id}: depends on itself")
264
+ elif dependency not in seen:
265
+ report.error(f"{task_id}: depends on unknown task {dependency}")
266
+ elif order[dependency] > order[task_id]:
267
+ report.error(
268
+ f"{task_id}: forward dependency on {dependency} "
269
+ "- reorder tasks so dependencies come first"
270
+ )
271
+ elif phases.get(dependency, 0) > phases.get(task_id, 0):
272
+ report.error(
273
+ f"{task_id} (phase {phases.get(task_id, 0)}): depends on "
274
+ f"{dependency} from phase {phases[dependency]} "
275
+ "- a phase never depends on a later one"
276
+ )
277
+
278
+ cycle = detect_cycle(graph)
279
+ if cycle:
280
+ report.error(f"dependency cycle detected: {' -> '.join(cycle)}")
281
+ else:
282
+ report.ok("dependency graph is acyclic")
283
+
284
+ independent = [task_id for task_id, deps in graph.items() if not deps]
285
+ if len(independent) > 1:
286
+ report.ok(
287
+ f"{len(independent)} task(s) without dependencies - candidates for parallel work "
288
+ "(see task-graph-engineering.md)"
289
+ )
290
+
291
+ owners: dict[str, list[str]] = {}
292
+ for task_id, paths in files_by_task.items():
293
+ for path in paths:
294
+ owners.setdefault(path, []).append(task_id)
295
+
296
+ for path, writers in owners.items():
297
+ if len(writers) < 2:
298
+ continue
299
+ for index, left in enumerate(writers):
300
+ for right in writers[index + 1 :]:
301
+ if reachable(graph, left, right) or reachable(graph, right, left):
302
+ continue
303
+ report.error(
304
+ f"{left} and {right} both write '{path}' with no dependency "
305
+ "between them - merge the tasks or make one depend on the other"
306
+ )
307
+
308
+ if spec_text is not None:
309
+ spec_requirements = requirement_ids(visible_markdown(spec_text))
310
+ if spec_requirements:
311
+ missing = [
312
+ requirement_id
313
+ for requirement_id in spec_requirements
314
+ if requirement_id not in covered_requirements
315
+ ]
316
+ if missing:
317
+ for requirement_id in missing:
318
+ report.error(
319
+ f"spec requirement {requirement_id} has no task "
320
+ "- map it in a task Requirement field"
321
+ )
322
+ else:
323
+ report.ok(
324
+ f"all {len(spec_requirements)} spec requirement(s) covered by tasks"
325
+ )
326
+
327
+ placeholders = find_placeholders(text)
328
+ if placeholders:
329
+ for item in placeholders[:10]:
330
+ report.error(f"unresolved placeholder at {item}")
331
+ else:
332
+ report.ok("no unresolved placeholders")
333
+
334
+ if len(seen) >= 3 and feature_dir is not None:
335
+ graph_path = feature_dir / "task-graph.md"
336
+ if graph_path.is_file():
337
+ report.ok("task-graph.md present for 3+ task breakdown")
338
+ else:
339
+ report.error(
340
+ "3+ tasks require task-graph.md - draw the DAG before approval "
341
+ "(see task-graph-engineering.md)"
342
+ )
343
+
344
+ return report
345
+
346
+
347
+ def _load_sibling_spec(tasks_path: Path) -> str | None:
348
+ spec_path = tasks_path.parent / "spec.md"
349
+ if spec_path.is_file():
350
+ return spec_path.read_text(encoding="utf-8")
351
+ return None
352
+
353
+
354
+ def main(argv: list[str] | None = None) -> int:
355
+ parser = argparse.ArgumentParser(description="Validate a feature tasks.md")
356
+ parser.add_argument(
357
+ "tasks",
358
+ nargs="?",
359
+ help="feature name, feature directory, or path to tasks.md",
360
+ )
361
+ parser.add_argument(
362
+ "--strict",
363
+ action="store_true",
364
+ help="treat warnings as blocking failures",
365
+ )
366
+ args = parser.parse_args(argv)
367
+
368
+ path, text = resolve_artifact(args.tasks, "tasks.md", GATE)
369
+ report = build_report(
370
+ str(path),
371
+ text,
372
+ spec_text=_load_sibling_spec(path),
373
+ feature_dir=path.parent,
374
+ )
375
+ return report.emit(strict=args.strict)
376
+
377
+
378
+ if __name__ == "__main__":
379
+ sys.exit(main())
@@ -0,0 +1,221 @@
1
+ ---
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".
4
+ ---
5
+
6
+ # Agent Architecture (Hub)
7
+
8
+ Spec-Driven Development (SDD) guardrails for AI-assisted software engineering.
9
+ Replaces "Vibe Coding" with adaptive phases backed by persistent memory, sister skills, and gates enforced by code.
10
+
11
+ **Token cost.** Load a working set, not the archive — see `references/context-limits.md`. Progressive phase loading is ~70% fewer skill tokens than dumping hub + all references + sister skills every turn; a Medium feature is typically ~80% cheaper in skill tokens than naive full reloads.
12
+
13
+ This file is the contract and the map. Phase procedures live in `references/`; cross-cutting concerns live in sister skills.
14
+
15
+ ## Critical Rules (read before acting)
16
+
17
+ **Reference files.** Phase procedures live in `references/` next to this file (`.cursor/skills/references/`, `.claude/skills/references/`). Read a reference **completely** before acting on it. Never act on a partial read. Load the working set per `references/context-limits.md` — one feature at a time, current phase only.
18
+
19
+ **Gate scripts.** Structural gates live in `.specs/guardrails/scripts/` at the project root. Run them with `python3`; never assume a project-local `scripts/` directory belongs to Spec Guardrails.
20
+
21
+ **Execution contract — non-negotiable, holds even if no reference file is open:**
22
+
23
+ 1. **Test-First Imperative** — Tests derive from the spec's acceptance criteria and assert spec-defined outcomes. They never mirror the implementation. No production code before spec and derived tests are approved.
24
+ 2. **Gate before done** — A task is complete only when the project harness (tests, linter, compiler) passes. The runner decides, never self-assessment.
25
+ 3. **One atomic commit per task** — Mark the task complete in `tasks.md` and include that update in the same commit. Never batch tasks; never weaken, skip, or delete tests to make them pass.
26
+ 4. **Author ≠ verifier** — After the last task, `/verify` runs with a fresh, clean context that never wrote the code. It is mandatory, not prompted.
27
+ 5. **Blast radius (git tiers)** — Approving a spec or tasks authorizes **Tier 0** local work only. Higher tiers need owner go-ahead.
28
+
29
+ | Tier | Authorized by spec/tasks approval | Owner go-ahead required |
30
+ | --- | --- | --- |
31
+ | **0 — Local sandbox** | `feature-init`, feature folder, `git checkout -b feat/NNN-slug`, local commits (code + `.specs/`) | — |
32
+ | **1 — Share** | — | `git push`, open/update PR for this feature |
33
+ | **2 — External impact** | — | merge to default branch, deploy/release, force-push, production data or secrets |
34
+
35
+ Quick tier skips dedicated feature branches — commit on the current branch. See `git-handoff.md` for phase triggers.
36
+
37
+ ## Deterministic Gates
38
+
39
+ Structural gates run **before** owner review, so they cannot drift when the model forgets a step.
40
+
41
+ | When | Command |
42
+ | --- | --- |
43
+ | Before `/specify` (Medium+) | `npx @luizsantiago/spec-guardrails feature-init "<description>"` (Tier 0) |
44
+ | Optional project config | `init-config --preset node-ts` or `install --preset python` (see `preset list`) |
45
+ | Before confirming a spec | `python3 .specs/guardrails/scripts/validate_spec.py [feature]` |
46
+ | Before approving tasks | `python3 .specs/guardrails/scripts/analyze_artifacts.py [feature]` |
47
+ | Before presenting tasks for approval | `python3 .specs/guardrails/scripts/validate_tasks.py [feature]` |
48
+ | On each commit | `python3 .specs/guardrails/scripts/check_commit.py --message "<message>"` |
49
+ | Before declaring a feature done | `python3 .specs/guardrails/scripts/validate_state.py [feature]` |
50
+ | After Verify PASS | `npx @luizsantiago/spec-guardrails archive-feature [feature]` (Tier 0) |
51
+ | Before a phase procedure (optional) | `npx @luizsantiago/spec-guardrails phase-context <phase>` |
52
+ | After a FAIL verdict | `python3 .specs/guardrails/scripts/lessons.py add --source .specs/features/[feature]/validation.md` |
53
+
54
+ Gates accept a feature name, a feature directory, or a path to the artifact. With no argument they auto-detect when the project has exactly one feature; with several they list candidates and exit 2. A spec is rejected unless every criterion uses `SHALL` or `MUST` and `## Assumptions` is present.
55
+
56
+ A **non-zero exit means STOP** — fix the artifact, then re-run the gate. Never continue past a failing gate.
57
+
58
+ **Degraded mode.** If Python 3 or shell execution is unavailable, say so once, then perform the same checks by reading the artifact against the reference checklist. Degraded mode never lowers the standard; it only changes who runs the check.
59
+
60
+ ## Phase Map
61
+
62
+ ```
63
+ EXPLORE (optional) → SPECIFY → DISCUSS (conditional) → DESIGN (optional) → TASKS (optional) → ANALYZE → EXECUTE (loop) → VERIFY → ARCHIVE
64
+ ```
65
+
66
+ | Phase | Required | Reference | Sister skill | Gate |
67
+ | --- | --- | --- | --- | --- |
68
+ | **Explore** | Optional | `references/explore.md` | — | — |
69
+ | **Constitution** | Once per project | `references/constitution.md` | — | — |
70
+ | **Specify** | Yes | `references/specify.md` | — | `validate_spec.py` |
71
+ | **Discuss** | Conditional | `references/discuss.md` | — | — |
72
+ | **Design** | No | `references/design.md` | — | — |
73
+ | **Tasks** | No | `references/tasks.md` | `task-graph-engineering.md` | `validate_tasks.py` |
74
+ | **Analyze** | Before task approval | `references/analyze.md` | — | `analyze_artifacts.py` |
75
+ | **Execute** | Yes | `references/implement.md` | `engineering-standards.md` | `check_commit.py` |
76
+ | **Verify** | Yes | `references/validate.md` | `security-review.md` | `validate_state.py` |
77
+ | **Archive** | After Verify PASS | `references/archive.md` | `git-handoff.md` | `archive-feature` |
78
+ | **Converge** | On drift | `references/converge.md` | — | `analyze_artifacts.py` |
79
+ | **Handoff** | Yes | `references/memory.md` | `git-handoff.md` | — |
80
+ | **Quick** | Alternative | `references/quick-mode.md` | — | `check_commit.py` |
81
+ | **Context** | Always | `references/context-limits.md` | — | — |
82
+ | **Sub-agents** | When batched | `references/sub-agents.md` | `task-graph-engineering.md` | — |
83
+ | **Lessons** | On FAIL | `references/lessons.md` | — | `lessons.py` |
84
+
85
+ Context is a load rule, not a pipeline phase. Read it when the session is long or the feature has more than a handful of tasks. Sub-agents is the Execute scaling protocol — offer only when the task graph needs more than one batch; see `references/sub-agents.md`. Lessons is a FAIL-path step, not a sequential phase — see `references/lessons.md`.
86
+
87
+ ## Conditional sister skills
88
+
89
+ Not in the default phase-map cell. Load only on `/verify` after `validate.md` + `security-review.md`, and **at most one in context at a time**.
90
+
91
+ | Skill | Load when | Skip when |
92
+ | --- | --- | --- |
93
+ | `appsec.md` | **Complex**, or auth / payments / PII / secrets / upload / SSRF / network trust boundary | Quick; Simple without those surfaces; copy/docs/styling |
94
+ | `qa-strategy.md` | **Complex**, or multi-step user-facing flow, or owner asked for regression/QA | Quick; Simple one-file; evidence-or-zero alone is enough |
95
+
96
+ **Sequence.** If both triggers fire: AppSec → write `## AppSec` → **drop** `appsec.md` from the working set → QA → write `## QA`. Never load both together. Neither section is enforced by `validate_state.py` (verifier judgment).
97
+
98
+ ## Complexity Router
99
+
100
+ Complexity determines depth. Do not run every phase on every change.
101
+
102
+ | Tier | Scope | Path |
103
+ | --- | --- | --- |
104
+ | **Quick** | ≤3 files, no design decisions, no new dependencies | `references/quick-mode.md` — describe, implement, verify, commit |
105
+ | **Simple** | 2–5 files, localized change | Specify → Execute → Verify |
106
+ | **Medium** | New feature, <10 tasks | Specify → Tasks → Execute → Verify |
107
+ | **Complex** | New architecture, API surface, infra | Specify → Discuss → Design → Tasks → Execute → Verify |
108
+ | **Parallel** | Splittable work, multiple agents | Above + `/task-graph` per `task-graph-engineering.md` |
109
+
110
+ **Hub Medium vs gate Medium+.** Router tiers above choose phase depth (hub **Medium** = new feature, under 10 tasks). The completion gate’s **Medium+** is separate: `design.md` with content, **or** 4+ tasks, **or** 2+ phases — that is when a discrimination-sensor outcome is blocking. A hub-Medium feature with only three tasks can be below gate Medium+.
111
+
112
+ **Rules**
113
+
114
+ - **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 only `check_commit.py` as a structural gate.
115
+ - **Design is skipped** when there are no architectural decisions and no new patterns.
116
+ - **Tasks is skipped** when there are ≤3 obvious steps.
117
+ - **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.
118
+ - **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.
119
+
120
+ When in doubt, start at **Medium** and drop phases only with owner approval.
121
+
122
+ ## Persistent Memory (`.specs/`)
123
+
124
+ | Path | Purpose |
125
+ | --- | --- |
126
+ | `.specs/STATE.md` | Decision log (`AD-NNN`) and handoff snapshot |
127
+ | `.specs/lessons.json` | Canonical lessons store, owned by `lessons.py` |
128
+ | `.specs/LESSONS.md` | Generated playbook of confirmed lessons — read, never write |
129
+ | `.specs/project/PROJECT.md` | Vision, stack, constraints (when the project defines them) |
130
+ | `.specs/project/CONSTITUTION.md` | Governing principles (when Constitution ran) |
131
+ | `.specs/project/ROADMAP.md` | Milestones and feature status |
132
+ | `.specs/config.yaml` | Optional project context and per-phase rules |
133
+ | `.specs/domains/[domain]/spec.md` | Long-lived domain truth after Archive |
134
+ | `.specs/quick/NNN-slug/` | Quick-mode tasks and summaries |
135
+ | `.specs/features/[feature]/spec.md` | Requirements (use `NNN-slug` from `feature-init`) |
136
+ | `.specs/features/[feature]/context.md` | Owner decisions for gray areas (only when Discuss ran) |
137
+ | `.specs/features/[feature]/design.md` | Architecture (Complex tier) |
138
+ | `.specs/features/[feature]/tasks.md` | Atomic task breakdown |
139
+ | `.specs/features/[feature]/task-graph.md` | Job DAG and parallel groups (when applicable) |
140
+ | `.specs/features/[feature]/validation.md` | Independent verification report |
141
+ | `.specs/guardrails/scripts/` | Deterministic gate scripts |
142
+
143
+ **Create artifacts lazily.** Write a file only when its phase actually produces content. Never scaffold an empty `design.md`, `tasks.md`, or `context.md` — an empty file claims a phase ran when it did not. Absence is the correct state for a skipped phase.
144
+
145
+ Read `STATE.md` at session start; update it at session end. See `references/memory.md` and `git-handoff.md`.
146
+
147
+ ## Loop Engineering & Harness
148
+
149
+ - **Correction Loop** — If the project harness fails, fix and retest up to 3 times before escalating to the owner.
150
+ - **Operational Harness** — Quality is enforced by test runners, linters, and compilers, never by AI self-declaration.
151
+ - **Fix → re-verify** — Gaps found in Verify become fix tasks; the loop is bounded to 3 iterations before escalating.
152
+
153
+ ## Knowledge Verification Chain
154
+
155
+ Follow in strict order when making any technical decision:
156
+
157
+ 1. **Codebase** — Conventions and patterns already in use
158
+ 2. **Project docs** — README, `docs/`, `.specs/STATE.md` decisions
159
+ 3. **MCP / Context** — Up-to-date library documentation via tools
160
+ 4. **Web search** — Official docs and community patterns
161
+ 5. **Uncertainty** — Say "I don't know" and flag it. Never invent APIs or behaviors.
162
+
163
+ Never skip to step 5 while steps 1–4 are available. Fabrication cascades through design, tasks, and implementation.
164
+
165
+ ## Output Behavior
166
+
167
+ - **Do the work; do not narrate the machinery.** Produce the artifact instead of announcing the phase.
168
+ - **Match effort to the work.** Heavy reasoning for design and ambiguity; fast execution for mechanical tasks.
169
+ - **Write artifacts in a plain, decided voice.** Lead with the verdict; cut filler and hedging.
170
+ - **Artifacts in English** — code, tests, commits, and `.specs/` documents (see `engineering-standards.md`). Chat language is the owner's personal setting, not guardrails rule.
171
+
172
+ ## Model Selection
173
+
174
+ - **Planning** (Specify, Discuss, Design, Tasks): high-reasoning models
175
+ - **Execution loop**: fast, cost-effective models
176
+ - **Verifier**: mid-to-high tier — it performs adversarial reasoning and designs mutants
177
+
178
+ ## Sister Skills
179
+
180
+ | Skill | Layer |
181
+ | --- | --- |
182
+ | `task-graph-engineering.md` | Topology — task DAG, parallelism, diamond verify |
183
+ | `engineering-standards.md` | Quality — secure coding, one writer per file, artifact language |
184
+ | `security-review.md` | Verification — OWASP checklist for `/verify` |
185
+ | `appsec.md` | Conditional AppSec — threat sketch; Complex / attack surface only |
186
+ | `qa-strategy.md` | Conditional QA — smoke/regression; after AppSec if both apply |
187
+ | `code-simplify.md` | Conditional simplify — after A–D on Medium+ or owner ask; no behavior change |
188
+ | `ship-ready.md` | Conditional ship checklist — owner ask only; does not authorize push |
189
+ | `git-handoff.md` | Persistence — git sync, STATE template, session handoff |
190
+
191
+ Project rules: `.cursor/rules/engineering-baseline.mdc` (always applied in Cursor).
192
+
193
+ ## Optional companion: Full Stack Floor Map
194
+
195
+ When [`@luizsantiago/fullstack-floor-map`](https://www.npmjs.com/package/@luizsantiago/fullstack-floor-map) is installed, **Execute** may load one **Lane** layer manual and at most one catalog specialist per turn; **`/verify` stays Guardrails-only** (no Lane manuals, no catalog). Floor Map **0.5.0 (planned)** adds **Desk** memory under `.specs/desks/` for specialist continuity and handoff — companion-owned, not Guardrails gates. Pairing contract: [Companion: Full Stack Floor Map](https://github.com/luizssantiago92/spec-guardrails/blob/main/docs/guide/Companion-fullstack-floor-map.md).
196
+
197
+ ## Commands
198
+
199
+ | Command | Reference | Action |
200
+ | --- | --- | --- |
201
+ | `/explore` | `references/explore.md` | Think through ideas before Specify |
202
+ | `/project-init` | `references/project-init.md` | Brownfield: map repo → PROJECT + domain stubs |
203
+ | `/constitution` | `references/constitution.md` | Create project governing principles |
204
+ | `/specify` | `references/specify.md` | `feature-init` then requirements; EARS; delta specs |
205
+ | `/discuss` | `references/discuss.md` | Resolve gray areas into `context.md` |
206
+ | `/plan` | `references/design.md` | Create technical design |
207
+ | `/tasks` | `references/tasks.md` | Atomic breakdown; coverage matrix (authoring) |
208
+ | `/analyze` | `references/analyze.md` | Cross-artifact consistency before task approval |
209
+ | `/task-graph` | `task-graph-engineering.md` | Draw or revise the job DAG |
210
+ | `/loop` | `references/implement.md` | Orchestrate Execute — `loop-plan`, parallel sub-agents, adequacy A–D |
211
+ | `/verify` | `references/validate.md` | Independent validation; lean UAT; conditional AppSec/QA |
212
+ | `/converge` | `references/converge.md` | Reassess drift; append remaining tasks |
213
+ | `/archive` | `references/archive.md` | Fold verified feature into domain truth |
214
+ | `/quick` | `references/quick-mode.md` | Express lane for ≤3-file changes (no feature branch) |
215
+ | `/handoff` | `references/memory.md` | Update STATE, commit `.specs/` (Tier 0; no push) |
216
+ | `/sync-spec` | `git-handoff.md` | Commit current feature artifacts only |
217
+ | `/lessons` | `references/lessons.md` | Record or load grounded lessons |
218
+
219
+ ## Credits
220
+
221
+ Lineage and inspiration (CC-BY / MIT notices): see the repository [Credits](https://github.com/luizssantiago92/spec-guardrails#credits) — TLC Spec-Driven, Addy Osmani agent-skills, graph-engineering.
@@ -0,0 +1,83 @@
1
+ # AppSec
2
+
3
+ Lean application-security pass for **Complex** work or features with a real attack surface. Complements `security-review.md` (OWASP checklist on `/verify`) — it does **not** replace that checklist.
4
+
5
+ **Judgment only.** `validate_state.py` does not require an `## AppSec` section. A skip with reason is valid. This skill does not make the product “secure”; it structures a short threat look and when to escalate to a human.
6
+
7
+ ## When to Use
8
+
9
+ Load during `/verify` **after** the base Verify steps (`validate.md` + `security-review.md`) when **any** of:
10
+
11
+ - Hub tier is **Complex**
12
+ - Feature touches auth, sessions, tokens, payments, PII, secrets, file upload, SSRF/URL fetch, or a network trust boundary
13
+
14
+ ## When NOT to Use
15
+
16
+ Do **not** load on Quick, Simple without the surfaces above, copy/docs, or pure styling. Record:
17
+
18
+ `AppSec: skipped — no Complex tier and no auth/PII/payment/network surface`
19
+
20
+ Never load this skill in the same working set as `qa-strategy.md`. If both triggers fire: finish AppSec, **drop** this file from context, then load QA.
21
+
22
+ ## Relationship to security-review
23
+
24
+ | Concern | Where |
25
+ | --- | --- |
26
+ | OWASP checklist / lightweight path | `security-review.md` (always on Verify) |
27
+ | Threat sketch, boundaries, escalate | **This skill** (conditional) |
28
+
29
+ Do not paste the OWASP list here. Reuse the Security Review result; deepen only the boundary and abuse cases.
30
+
31
+ ## Procedure (about 15 minutes)
32
+
33
+ ### 1. Threat sketch
34
+
35
+ Write briefly (in `validation.md` under `## AppSec`, or in `design.md` if Design already captured it):
36
+
37
+ - **Assets** — what must stay confidential or integral (tokens, PII, money, admin actions)
38
+ - **Actors** — anonymous, authenticated user, admin, external service
39
+ - **Trust boundaries** — browser ↔ API, API ↔ DB, API ↔ third party
40
+ - **Top 3 abuse cases** — concrete misuse (IDOR on resource X, token reuse, inject into field Y)
41
+
42
+ ### 2. Focus list (pass / fail / N/A + one-line note)
43
+
44
+ Check only what the diff touches:
45
+
46
+ | Focus | Ask |
47
+ | --- | --- |
48
+ | AuthZ / IDOR | Can user A reach user B’s resource by changing an id? |
49
+ | Secrets | Credentials or keys only in env / secret store — not source, logs, or client storage? |
50
+ | Injection / XSS | User input parameterized or escaped on the paths this feature added? |
51
+ | Critical deps | New or bumped deps with known critical/high issues addressed or documented? |
52
+ | PII in logs | New log/response paths avoid raw PII? |
53
+
54
+ For checklist depth, return to `security-review.md`.
55
+
56
+ ### 3. Escalate (stop and ask the owner)
57
+
58
+ Escalate instead of `Result: pass` when the feature introduces or materially changes:
59
+
60
+ - Payment capture or money movement
61
+ - Homegrown cryptography
62
+ - New multi-tenant isolation
63
+ - “We are not sure” on a trust boundary the owner must accept
64
+
65
+ ## Output shape
66
+
67
+ ```markdown
68
+ ## AppSec
69
+ - Applied: yes | skipped — [reason]
70
+ - Boundaries: [browser↔API / …]
71
+ - Top risks: [1], [2], [3]
72
+ - Focus: authZ … | secrets … | injection … | deps … | PII logs …
73
+ - Result: pass | fail | escalate
74
+ ```
75
+
76
+ `fail` or `escalate` → Gaps bullet + fix or owner decision before `Verdict: PASS` (verifier judgment; not a structural gate).
77
+
78
+ ## Related
79
+
80
+ - `security-review.md` — OWASP Verify checklist
81
+ - `references/validate.md` — Verify procedure; load AppSec then drop before QA
82
+ - `references/context-limits.md` — at most one conditional sister in context
83
+ - [Gate stability](https://github.com/luizssantiago92/spec-guardrails/blob/main/prd/gate-stability.md) — AppSec is non-guarantee / judgment