@event4u/agent-config 8.6.0 → 8.8.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.
Files changed (118) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +81 -0
  3. package/CONTRIBUTING.md +25 -0
  4. package/README.md +7 -4
  5. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
  6. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +28 -16
  7. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +15 -0
  8. package/dist/agent-src/rules/active-remediation.md +92 -0
  9. package/dist/agent-src/rules/broken-access-control.md +106 -0
  10. package/dist/agent-src/rules/communication-through-line.md +42 -0
  11. package/dist/agent-src/rules/decision-revisit-gate.md +8 -0
  12. package/dist/agent-src/rules/minimal-safe-diff.md +18 -1
  13. package/dist/agent-src/rules/prefer-enums-over-literals.md +65 -0
  14. package/dist/agent-src/rules/senior-engineering-discipline.md +82 -0
  15. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +105 -0
  16. package/dist/agent-src/skills/ai-code-blindspots/evals/triggers.json +52 -0
  17. package/dist/agent-src/skills/authz-review/SKILL.md +21 -4
  18. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +20 -0
  19. package/dist/agent-src/skills/frontend-render-security/SKILL.md +104 -0
  20. package/dist/agent-src/skills/frontend-render-security/evals/triggers.json +52 -0
  21. package/dist/agent-src/skills/roadmap-writing/SKILL.md +7 -0
  22. package/dist/agent-src/skills/skill-writing/SKILL.md +4 -0
  23. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +100 -0
  24. package/dist/agent-src/skills/supply-chain-intake/evals/triggers.json +52 -0
  25. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +43 -2
  26. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  27. package/dist/agent-src/templates/roadmaps.md +24 -0
  28. package/dist/discovery/deprecation-report.md +1 -1
  29. package/dist/discovery/discovery-manifest.json +196 -16
  30. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  31. package/dist/discovery/discovery-manifest.summary.md +3 -3
  32. package/dist/discovery/orphan-report.md +1 -1
  33. package/dist/discovery/packs.json +13 -5
  34. package/dist/discovery/trust-report.md +11 -11
  35. package/dist/discovery/workspaces.json +12 -4
  36. package/dist/install/emit_host_rules_cli.js +185 -0
  37. package/dist/install/emit_host_rules_cli.js.map +1 -0
  38. package/dist/install/install.mjs +11 -2
  39. package/dist/install/plan.js +4 -1
  40. package/dist/install/plan.js.map +1 -1
  41. package/dist/install/rule_scope.js +109 -0
  42. package/dist/install/rule_scope.js.map +1 -0
  43. package/dist/install/rule_scope_cli.js +77 -0
  44. package/dist/install/rule_scope_cli.js.map +1 -0
  45. package/dist/install/wizard-plan.js +14 -1
  46. package/dist/install/wizard-plan.js.map +1 -1
  47. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  48. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
  49. package/dist/mcp/registry-manifest.json +4 -4
  50. package/dist/router.json +1 -1
  51. package/dist/scripts/_lib/agent_settings.js +963 -0
  52. package/dist/scripts/_lib/agent_settings.js.map +1 -0
  53. package/dist/scripts/_lib/agent_src.js +607 -0
  54. package/dist/scripts/_lib/agent_src.js.map +1 -0
  55. package/dist/scripts/_lib/claude_settings_hooks.js +209 -0
  56. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -0
  57. package/dist/scripts/_lib/model_tier.js +64 -0
  58. package/dist/scripts/_lib/model_tier.js.map +1 -0
  59. package/dist/scripts/_lib/script_output.js +140 -0
  60. package/dist/scripts/_lib/script_output.js.map +1 -0
  61. package/dist/scripts/_lib/user_global_paths.js +297 -0
  62. package/dist/scripts/_lib/user_global_paths.js.map +1 -0
  63. package/dist/scripts/condense.js +2181 -0
  64. package/dist/scripts/condense.js.map +1 -0
  65. package/dist/server/routes/install.js +18 -2
  66. package/dist/server/routes/install.js.map +1 -1
  67. package/dist/server/schemas/settings.js +4 -1
  68. package/dist/server/schemas/settings.js.map +1 -1
  69. package/docs/CLAIMS.md +30 -2
  70. package/docs/architecture.md +2 -2
  71. package/docs/catalog.md +14 -0
  72. package/docs/contracts/mcp-tool-inventory.md +31 -30
  73. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +6 -0
  74. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +2 -2
  75. package/docs/decisions/ADR-117-subagents-auto-default-on.md +88 -0
  76. package/docs/decisions/INDEX.md +1 -0
  77. package/docs/experiences/content_creator.md +2 -0
  78. package/docs/experiences/finance.md +2 -0
  79. package/docs/experiences/founder.md +2 -0
  80. package/docs/experiences/ops.md +2 -0
  81. package/docs/featured-skills.md +2 -2
  82. package/docs/getting-started-by-role.md +1 -1
  83. package/docs/getting-started.md +1 -1
  84. package/docs/governance-advantage.md +1 -1
  85. package/docs/guidelines/agent-infra/memory-access.md +12 -0
  86. package/docs/proof.md +45 -3
  87. package/docs/release-runbook.md +138 -0
  88. package/docs/second-brain-scope.md +73 -0
  89. package/docs/succession.md +80 -0
  90. package/package.json +2 -2
  91. package/src/config/agent-settings.template.yml +29 -8
  92. package/src/install/emit_host_rules_cli.ts +190 -0
  93. package/src/install/plan.ts +11 -1
  94. package/src/install/rule_scope.ts +120 -0
  95. package/src/install/rule_scope_cli.ts +79 -0
  96. package/src/install/wizard-plan.ts +22 -1
  97. package/src/scripts/_lib/orchestration_gate.ts +12 -8
  98. package/src/scripts/_lib/orchestration_savings.ts +127 -0
  99. package/src/scripts/build_proof.ts +38 -0
  100. package/src/scripts/chat_history.ts +79 -7
  101. package/src/scripts/check_trigger_eval_presence.ts +171 -0
  102. package/src/scripts/domain_soundness_status.ts +236 -0
  103. package/src/scripts/generate_index.ts +14 -0
  104. package/src/scripts/hot_context_hook.ts +47 -2
  105. package/src/scripts/install.sh +118 -4
  106. package/src/scripts/lint_behavioural_eval_freshness.ts +140 -0
  107. package/src/scripts/mcp_server/consumer_tool_catalog.json +22 -3
  108. package/src/scripts/mcp_server/tools.ts +159 -1
  109. package/src/scripts/memory_lookup.ts +184 -6
  110. package/src/scripts/memory_replay.ts +285 -0
  111. package/src/scripts/orchestration_savings_report.ts +100 -0
  112. package/src/scripts/schemas/domain-truth.schema.json +94 -0
  113. package/src/scripts/schemas/evals.schema.json +113 -0
  114. package/src/scripts/second_brain_score.ts +186 -0
  115. package/src/scripts/session_memory_index.ts +94 -0
  116. package/src/scripts/skill_eval_coverage.ts +318 -0
  117. package/src/scripts/trigger_eval_grandfather.json +226 -0
  118. package/src/server/schemas/settings.ts +11 -2
@@ -0,0 +1,65 @@
1
+ ---
2
+ type: "auto"
3
+ tier: "2b"
4
+ alwaysApply: false
5
+ description: "Modeling a field/property with multiple non-boolean states — prefer an enum over bare string/numeric literals; found old-style literals where an enum fits → note, finish the task, ask after"
6
+ triggers:
7
+ - intent: "modeling a field with multiple states"
8
+ - intent: "adding a status or type field"
9
+ - keyword: "enum"
10
+ - keyword: "status"
11
+ - keyword: "kind"
12
+ - keyword: "category"
13
+ - keyword: "migration"
14
+ workspaces: [engineering]
15
+ packs: [engineering-base]
16
+ ---
17
+
18
+ # Prefer Enums Over Literal Values
19
+
20
+ When a field/property holds **two or more non-boolean states** — strings (`'active'`, `'pending'`, `'archived'`) or meaningful numeric codes (priority `1`/`2`/`3`, tier levels) — model it as an **enum**, not a bare string/int. An enum names the value in code (readable), makes it greppable + safe to rename, gives exhaustiveness/typo safety, and makes filtering + editing relations far easier than raw literals scattered across the codebase.
21
+
22
+ ## The Iron Law
23
+
24
+ ```
25
+ A FIELD WITH MULTIPLE NON-BOOLEAN STATES IS AN ENUM, NOT A BARE STRING OR MAGIC NUMBER.
26
+ NAME THE VALUE IN CODE — NEVER SCATTER RAW LITERALS ('active', 2, 'PENDING') ACROSS THE CODEBASE.
27
+ FOUND OLD-STYLE LITERALS WHERE AN ENUM FITS? NOTE IT, FINISH THE TASK, THEN ASK — NEVER REFACTOR MID-FLOW.
28
+ ```
29
+
30
+ ## Prefer an enum when
31
+
32
+ - A multi-state **string** field — `status`, `type`, `role`, `kind`, `category`, `state` — with 2+ distinct values.
33
+ - A **numeric code that is a label, not a quantity** — priority `1/2/3`, severity levels, tiers.
34
+ - Any **closed set of values the code branches on** (`match` / `switch` / `if ===`).
35
+
36
+ ## Do NOT force an enum when
37
+
38
+ - A boolean (`true`/`false`) — a flag, not an enum.
39
+ - Open/unbounded free text — name, description, URL, comment.
40
+ - A genuine numeric **quantity** used in arithmetic — price, count, age, bytes.
41
+ - A set owned entirely by a volatile external system — unless you map it to your own enum at the boundary.
42
+
43
+ ## How (framework-neutral)
44
+
45
+ - Use the language's native construct: backed enum (PHP `enum Status: string`), string-literal union or `enum` (TypeScript), `enum.Enum` (Python), an enum/`CHECK`-constrained column or lookup table + FK (database).
46
+ - Persist the enum's backing value; reference the **named case** in code, never the raw literal.
47
+
48
+ ## Found old-style literals where an enum fits — defer, do not disrupt
49
+
50
+ Apply exactly this order so the current task's flow is never interrupted:
51
+
52
+ 1. **While doing the actual task**, if you pass code using raw string/numeric literals for a multi-state field where an enum fits, do **NOT** refactor inline (a drive-by edit — see `minimal-safe-diff`) and do **NOT** stop to ask mid-flow.
53
+ 2. **Note the site** — file:line + the literal set — in your working notes.
54
+ 3. **After the actual task is delivered**, surface the noted candidates as **one** numbered-options prompt (per `user-interaction`): list each enum-candidate site, ask whether to replace — all / selected / none.
55
+ 4. **Only on an explicit yes** do you refactor, as a **separate change** with its own scope (`downstream-changes` applies — update every caller, migration, test, serializer).
56
+
57
+ If the task itself is *adding* the field, apply the enum from the start — no deferral; the defer-and-ask flow is only for pre-existing literals you encounter.
58
+
59
+ ## See also
60
+
61
+ - [`improve-before-implement`](improve-before-implement.md) — the Strategy sniff test when a second branch on the same enum/string discriminator signals the enum should become a Strategy (`docs/guidelines/php/patterns/strategy.md`).
62
+ - [`minimal-safe-diff`](minimal-safe-diff.md) — why you don't drive-by-refactor the literals you find.
63
+ - [`scope-control`](scope-control.md) — replacing an existing pattern needs the user's yes first.
64
+ - [`active-remediation`](active-remediation.md) — the general fix-now / note-and-ask / follow-up-PR ladder; the enum defer-and-ask above is one instance of it.
65
+ - [`senior-engineering-discipline`](senior-engineering-discipline.md) — the "generalize, don't overfit" anchor this extends.
@@ -0,0 +1,82 @@
1
+ ---
2
+ type: "auto"
3
+ tier: "2b"
4
+ alwaysApply: false
5
+ description: "Writing/generating code — generalize (no overfit or tautological tests), supply the invisible cross-cutting controls the prompt didn't name, never invent an API/field/package"
6
+ triggers:
7
+ - intent: "writing or generating code"
8
+ - intent: "implementing a feature"
9
+ - keyword: "implement"
10
+ - keyword: "endpoint"
11
+ - keyword: "query"
12
+ - keyword: "migration"
13
+ - keyword: "component"
14
+ - keyword: "seeder"
15
+ - keyword: "dependency"
16
+ - keyword: "refactor"
17
+ routes_to:
18
+ - "skill:ai-code-blindspots"
19
+ - "skill:frontend-render-security"
20
+ - "skill:supply-chain-intake"
21
+ - "skill:testing-anti-patterns"
22
+ workspaces: [engineering]
23
+ packs: [engineering-base]
24
+ ---
25
+
26
+ # Senior Engineering Discipline
27
+
28
+ Dominant failure mode of AI-written code isn't ignorance — **omission**. Model writes happy-path code satisfying the literal task + passing shallow tests, silently drops the invisible cross-cutting controls a senior supplies from unstated context. Baseline (2025 studies): a large share of AI-generated code ships a known vulnerability even at high syntax-correctness, and automation bias makes devs trust it *more* while it's *less* safe. Write like a senior: general not overfit; invisible controls present; verified against real sources, not memory. Act like a senior **owner**, not a passive typist — work in the interest of the product, the user, and the company, especially on security and quality; see a problem → react (fix it if small and task-aligned, else ask — per [`active-remediation`](active-remediation.md)) — never look away.
29
+
30
+ ## The Iron Law
31
+
32
+ ```
33
+ CODE AND TESTS EXPRESS THE GENERAL RULE — NEVER OVERFIT TO A FIXED CASE.
34
+ SUPPLY THE INVISIBLE CROSS-CUTTING CONTROLS THE PROMPT DID NOT NAME.
35
+ NEVER INVENT AN API, FIELD, OR PACKAGE — VERIFY IT AGAINST A REAL SOURCE.
36
+ THE HAPPY PATH THAT PASSES ONE TEST IS NOT A FINISHED CHANGE.
37
+ ```
38
+
39
+ ## Three obligations, every time you write code
40
+
41
+ ### 1. Generalize — never overfit
42
+
43
+ - Implementation expresses the general rule, not a special case that happens to satisfy the example. Don't hardcode a derivable value; don't branch on one literal where a rule covers all inputs; don't paste a near-duplicate where an existing abstraction fits.
44
+ - Tests assert **general behavior**, never a fixed value or narrow regex passing only for one crafted input. Derive expected from inputs + seeded/random data — never hardcode what the code emits. Cover happy **and** boundary (empty / null / max / Unicode) **and** error **and**, on security paths, an abuse case. Lone happy-path assertion = tautology. → `skill:testing-anti-patterns`.
45
+
46
+ ### 2. Cover the invisible controls for what you touched
47
+
48
+ Before done, assert the unmentioned cross-cutting control is present, matched to surface:
49
+
50
+ - **endpoint / route** → authorization + tenant scope + input validation + rate limit; state-changing → CSRF + audit. (`security-sensitive-stop` gates the sensitive ones.)
51
+ - **database query / migration** → parameterized (never string-built), tenant-scoped, transaction around multi-step writes, reversible migration (expand-contract, never bare `DROP`), index on filtered / FK columns.
52
+ - **user-controlled render** → output-encode; never `dangerouslySetInnerHTML` / `v-html` / `innerHTML` / `eval` on non-constant input; no secret / token in client code. → `skill:frontend-render-security`.
53
+ - **file / outbound fetch** → path-confinement + SSRF allow-list; validate server-side, never client-only.
54
+ - **infra / IaC** → least-privilege (no `Action:*` / `Resource:*`), encryption at rest, no `0.0.0.0/0` to mgmt / DB ports, no hardcoded creds; real scanner (Checkov / Trivy) is the backstop — `plan` succeeding isn't.
55
+ - **new dependency** → verify it exists on the real registry before install (hallucinated / "slopsquatted" packages ~1 in 5 AI suggestions); pin + lockfile; CVE scan. → `skill:supply-chain-intake`.
56
+
57
+ Full surface→controls table + backstop greps: → `skill:ai-code-blindspots`.
58
+
59
+ ### 3. Verify — never assert from memory
60
+
61
+ No completion claim without exercising the real path (running endpoint / UI / test — not "the code looks right"). "Defined but not wired" (middleware written, never connected to the route) = **not done**. → `verify-before-complete`, `source-discovery-gate`.
62
+
63
+ ## When it fires
64
+
65
+ Any turn that writes, generates, or modifies code — feature, endpoint, query, migration, component, test, seeder, dependency, infra resource.
66
+
67
+ ## When NOT to fire
68
+
69
+ - Prose / docs / config-only edits, no code behavior.
70
+ - One-line rename / typo / formatting.
71
+ - User fenced the scope ("just this one line", "skip the checks").
72
+
73
+ ## Relationship to existing rules
74
+
75
+ Cross-cutting **anchor**; depth lives elsewhere and this routes to it. Doesn't replace `security-sensitive-stop` (threat-model before editing auth / billing / tenant / secret paths), `minimal-safe-diff` (smallest change), `think-before-action` (analyze first), `downstream-changes` (update all callers), `output-discipline` (no placeholder / TODO code). When one fires it wins on its subject; this adds the "generalized + invisible controls?" gate on top.
76
+
77
+ ## See also
78
+
79
+ - [`ai-code-blindspots`](../skills/ai-code-blindspots/SKILL.md) — surface→controls checklist + backstop greps.
80
+ - [`frontend-render-security`](../skills/frontend-render-security/SKILL.md), [`supply-chain-intake`](../skills/supply-chain-intake/SKILL.md) — the two precision gaps.
81
+ - [`testing-anti-patterns`](../skills/testing-anti-patterns/SKILL.md) — overfit / tautological assertions + test-data realism.
82
+ - [`security-sensitive-stop`](security-sensitive-stop.md), [`engineering-safety-floor`](engineering-safety-floor.md), [`verify-before-complete`](verify-before-complete.md), [`source-discovery-gate`](source-discovery-gate.md).
@@ -0,0 +1,105 @@
1
+ ---
2
+ model_tier: medium
3
+ name: ai-code-blindspots
4
+ description: "Before finishing any code (endpoint, query, migration, render, file, infra, dependency, test) — the senior pre-ship checklist of invisible cross-cutting controls AI omits, with backstop greps"
5
+ domain: quality
6
+ workspaces:
7
+ - engineering
8
+ packs:
9
+ - engineering-base
10
+ ---
11
+
12
+ # ai-code-blindspots
13
+
14
+ AI writes the code the prompt literally asked for and stops. The controls a senior adds from unstated context — authorization, tenant scope, validation, transactions, encryption, rate limiting, output encoding, error handling, edge cases, package existence — are *invisible* to a single-file prompt, so they're the first thing dropped. They're also the classes pattern-linters miss. This skill is the pre-ship checklist: for the surface you wrote, assert the matching control is present, run the backstop grep, route to the deep skill when it needs more than a check.
15
+
16
+ A **routing + verification** layer — doesn't re-teach what the deep skills own. The "did I supply the invisible controls?" gate `senior-engineering-discipline` points at.
17
+
18
+ ## When to use
19
+
20
+ - You wrote (or are finishing) an endpoint, query, migration, UI render, file/network op, infra resource, dependency change, or test — before calling it done.
21
+ - Reviewing an AI-authored diff, asking "what did it silently omit?".
22
+ - The `senior-engineering-discipline` rule fired and routed here.
23
+
24
+ Do NOT use when: change is prose/docs/config-only, or a one-line rename — no code surface to check.
25
+
26
+ ## The Iron Law
27
+
28
+ ```
29
+ FOR EVERY SURFACE YOU TOUCHED, NAME THE INVISIBLE CONTROL AND CONFIRM IT IS PRESENT.
30
+ "NOT MENTIONED IN THE PROMPT" IS EXACTLY WHY IT IS MISSING — CHECK IT ANYWAY.
31
+ A CONTROL DEFINED BUT NOT WIRED IS ABSENT. A grep IS CHEAPER THAN A BREACH.
32
+ ```
33
+
34
+ ## Procedure
35
+
36
+ 1. List the surfaces this change touched (endpoint, query, migration, render, file/fetch, infra, dependency, test).
37
+ 2. For each surface, take its row below: assert every listed invisible control is present (with file:line), and run the matching backstop grep.
38
+ 3. For anything a grep flags or a check can't confirm, open the routed deep skill.
39
+ 4. Emit the per-surface confirmation (Output format); surface any unresolved gap to the user — never silently omit.
40
+
41
+ ## Surface → invisible controls → backstop
42
+
43
+ Run the row(s) for what you touched. The grep is a fast authoring-time backstop (not a substitute for reading the code); zero results = pass, a hit = look.
44
+
45
+ | Surface you wrote | Invisible controls to assert | Deep skill |
46
+ |---|---|---|
47
+ | **HTTP endpoint / route** | authorization (this principal may act on this resource — not just authenticated); tenant scope; **the three negative tests** (unauth→401, non-owner→403/404, cross-tenant→403/404); boundary input validation; rate limit actually wired; state-changing → CSRF + audit | `broken-access-control`, `authz-review`, `threat-modeling` |
48
+ | **DB query / ORM** | parameterized, never string-built; tenant predicate present; no `SELECT *` across a serialization boundary; N+1 avoided (eager load) | `source-discovery`, `security` |
49
+ | **Migration** | reversible (`down`); expand-contract for drop/rename (never a bare `DROP COLUMN` before code stops reading it); transaction; index on new FK/filter column | `engineering-safety-floor`, `migration-architect` |
50
+ | **User-controlled render (FE)** | output-encoded; no `dangerouslySetInnerHTML`/`v-html`/`innerHTML`/`eval` on non-constant input; no secret/token in client code; token not in `localStorage` | `frontend-render-security` |
51
+ | **File / outbound fetch** | path confined to an allowed base; SSRF allow-list + private-IP block on user-supplied URLs; size limit; server-side validation (never client-only) | `security`, `defense-in-depth` |
52
+ | **Infra / IaC** | least-privilege (no `Action:*`/`Resource:*`); encryption at rest; no `0.0.0.0/0` to mgmt/DB ports; no hardcoded creds; scanner-verified, not `plan`-verified | `engineering-safety-floor`, `terraform`, `secrets-management` |
53
+ | **New dependency** | exists on the real registry (not hallucinated); not typo-adjacent to a popular one; pinned + lockfile committed; CVE-scanned | `supply-chain-intake`, `dependency-upgrade` |
54
+ | **Secrets/credentials** | never a literal in source; env / secret-manager reference; password columns use bcrypt/argon2, never MD5/SHA | `secrets-management`, `domain-safety-pii` |
55
+ | **Test** | asserts general behavior; expected derived from inputs/seeded data, not hardcoded; boundary + error + abuse cases, not only happy path | `testing-anti-patterns`, `test-driven-development` |
56
+
57
+ ### Backstop greps (authoring-time, cross-stack)
58
+
59
+ ```bash
60
+ # Frontend insecure render / client secrets (React + Vue + vanilla)
61
+ rg -n 'dangerouslySetInnerHTML|v-html|\.innerHTML\s*=|\beval\(|new Function\('
62
+ rg -n 'NEXT_PUBLIC_.*(SECRET|KEY|TOKEN)|VITE_.*(SECRET|KEY)|localStorage\.setItem\([^)]*[Tt]oken'
63
+ # String-built SQL (concatenation / interpolation into a query)
64
+ rg -n 'query\(.*(\+|\$\{|`).*\)|(SELECT|INSERT|UPDATE|DELETE).*(\+|\$\{)'
65
+ # Hardcoded secrets / weak hashing
66
+ rg -n 'AKIA[0-9A-Z]{16}|sk_live_|AIza[0-9A-Za-z_\-]{35}|password\s*=\s*["\x27]|md5\(|sha1\(' .
67
+ # Infra: wildcard IAM / open ingress / disabled TLS
68
+ rg -n '"Action"\s*:\s*"\*"|"Resource"\s*:\s*"\*"|0\.0\.0\.0/0|InsecureSkipVerify|verify=false|curl.*\| *bash' .
69
+ ```
70
+
71
+ The greps name several ecosystems side by side on purpose: keep the ones for the stack in front of you; a hit means read that line, not auto-fix.
72
+
73
+ ## Output format
74
+
75
+ 1. A one-line per-surface confirmation for the change, e.g. `endpoint: authz ✓ tenant-scope ✓ rate-limit wired ✓ validation ✓`, naming file:line where each control lives (or `n/a — <reason>`).
76
+ 2. The backstop grep(s) run for the touched surface(s), with result (zero hits, or the hit + how resolved).
77
+ 3. For any control genuinely out of scope, an explicit note saying so — never a silent omission.
78
+
79
+ ## Gotcha
80
+
81
+ - **Defined-but-not-wired** is the signature AI failure: rate-limit middleware or CSRF guard is *written* but never attached to the route. Check is "wired on THIS path?", not "exists somewhere?".
82
+ - **Semantically-valid-but-ignored**: an IaC attribute the provider silently ignores (a made-up `encrypted = true` on a resource that doesn't support it) *reads* as a present control but does nothing — a scanner catches it, a code read doesn't.
83
+ - A green grep is necessary, not sufficient — proves the obvious anti-pattern absent, not that the control is correct. Read the seam for anything security-sensitive.
84
+ - Routes; doesn't replace the deep skill. For auth/billing/tenant/secret paths, `security-sensitive-stop` still requires a threat pass *before* editing.
85
+
86
+ ## Do NOT
87
+
88
+ - Do NOT treat "the prompt didn't ask for it" as a reason to omit a control — that's the exact blind spot.
89
+ - Do NOT mark a change done on a green happy-path test alone.
90
+ - Do NOT auto-rewrite a grep hit without reading it — some are intentional and belong in an allow-list with a reason.
91
+ - Do NOT duplicate the deep skills here — route to them.
92
+
93
+ ## Auto-trigger keywords
94
+
95
+ - invisible controls
96
+ - ai code blindspots
97
+ - pre-ship checklist
98
+ - what did the AI omit
99
+ - cross-cutting controls
100
+
101
+ ## See also
102
+
103
+ - [`senior-engineering-discipline`](../../rules/senior-engineering-discipline.md) — the anchor rule that routes here.
104
+ - [`frontend-render-security`](../frontend-render-security/SKILL.md), [`supply-chain-intake`](../supply-chain-intake/SKILL.md), [`testing-anti-patterns`](../testing-anti-patterns/SKILL.md).
105
+ - [`authz-review`](../authz-review/SKILL.md), [`threat-modeling`](../threat-modeling/SKILL.md), [`defense-in-depth`](../defense-in-depth/SKILL.md), [`source-discovery`](../source-discovery/SKILL.md), [`secrets-management`](../secrets-management/SKILL.md).
@@ -0,0 +1,52 @@
1
+ {
2
+ "skill": "ai-code-blindspots",
3
+ "last_eval": "2026-07-09",
4
+ "description": "5 should-trigger + 5 should-not-trigger queries. Should-trigger covers DE + EN phrasings for the pre-ship 'what invisible cross-cutting controls did the AI omit?' checklist across code surfaces. Should-not-trigger covers near-miss neighbours (deep single-surface skills, prose/docs edits, adversarial config review) whose vocabulary overlaps.",
5
+ "queries": [
6
+ {
7
+ "q": "I just wrote this endpoint — what invisible controls did I forget before I call it done?",
8
+ "trigger": true
9
+ },
10
+ {
11
+ "q": "run the senior pre-ship checklist over this diff for cross-cutting controls",
12
+ "trigger": true
13
+ },
14
+ {
15
+ "q": "what does AI-generated code usually omit here — authz, tenant scope, validation?",
16
+ "trigger": true
17
+ },
18
+ {
19
+ "q": "prüf meinen Code auf die unsichtbaren Kontrollen, die die KI weglässt",
20
+ "trigger": true
21
+ },
22
+ {
23
+ "q": "before I finish this migration and query, what am I blind to?",
24
+ "trigger": true
25
+ },
26
+ {
27
+ "q": "review this React component specifically for XSS and client secrets",
28
+ "trigger": false,
29
+ "note": "single frontend surface → frontend-render-security"
30
+ },
31
+ {
32
+ "q": "is this npm package safe to add?",
33
+ "trigger": false,
34
+ "note": "dependency intake → supply-chain-intake"
35
+ },
36
+ {
37
+ "q": "threat-model this new payments endpoint before I build it",
38
+ "trigger": false,
39
+ "note": "pre-build threat model → threat-modeling / security-sensitive-stop"
40
+ },
41
+ {
42
+ "q": "fix the typo in this README",
43
+ "trigger": false,
44
+ "note": "prose/docs edit — no code surface"
45
+ },
46
+ {
47
+ "q": "red-team my agent's CLAUDE.md and MCP config",
48
+ "trigger": false,
49
+ "note": "agent-config adversarial review → agent-security-review"
50
+ }
51
+ ]
52
+ }
@@ -48,8 +48,10 @@ Do NOT use when:
48
48
  [`security-audit`](../security-audit/SKILL.md)
49
49
  * The concern is a diff ready for review — route to
50
50
  [`judge-security-auditor`](../judge-security-auditor/SKILL.md)
51
- * The concern is response/log leakage rather than access gating — route to
52
- [`data-exposure-review`](../data-exposure-review/SKILL.md)
51
+ * The concern is PII leakage into logs specifically — route to
52
+ [`data-flow-mapper`](../data-flow-mapper/SKILL.md). (Role-based field-level
53
+ output filtering IS access gating — it stays in scope here + the vertical-BOPLA
54
+ section of [`broken-access-control`](../../rules/broken-access-control.md).)
53
55
  * The concern is implementing a control once identified — route to
54
56
  [`security`](../security/SKILL.md)
55
57
 
@@ -71,8 +73,8 @@ For every entrypoint, analyze the authorization chain and record what you find:
71
73
  | Authentication gate | Is login enforced? By which middleware / guard? |
72
74
  | Authorization layer | Which policy, gate, voter, or check? Which action/ability? |
73
75
  | Data scope | Does the query filter by current user / tenant / owner? |
74
- | Response filter | Are sensitive fields stripped (resource/serializer/DTO)? |
75
- | Tests | Is there a negative test (other-tenant / lower-role returns 403/404)? |
76
+ | Response filter | Are sensitive fields stripped **per role** via a role-scoped resource/serializer/DTO — never the raw model? (a driver role must not receive `price`) |
77
+ | Tests | Are the three negative tests present — unauthenticated → 401, non-owner → 403/404, cross-tenant → 403/404 (404 hides existence)? |
76
78
 
77
79
  Record **what is there**, not what should be there. Use file:line citations.
78
80
 
@@ -160,6 +162,17 @@ Runtime confirmation (e.g. *"reproduce the cross-tenant read against staging"*,
160
162
  authorization. Still need the policy + scope.
161
163
  * **Generic advice without file:line** — reject your own finding if you cannot
162
164
  cite the exact location.
165
+ * **The three negative tests are the security boundary.** Every protected
166
+ entrypoint needs unauthenticated → 401, authenticated-non-owner → 403/404,
167
+ and cross-tenant → 403/404. A happy-path 200 test proves nothing about access
168
+ control (this is BOLA / IDOR — OWASP API #1). Untested authz is also a
169
+ direct GDPR Art. 32 gap. See [`broken-access-control`](../../rules/broken-access-control.md).
170
+ * **Defense-in-depth so one miss can't leak** — stack ≥2 layers on sensitive
171
+ data: query-level ownership scoping (base scope injects the predicate) + a
172
+ centralized default-deny policy layer (a route with no declared policy is
173
+ denied, not silently open) + DB row-level security (Postgres RLS `FORCE ROW
174
+ LEVEL SECURITY`, tenant var via `SET LOCAL`) as the backstop for a forgotten
175
+ `WHERE tenant_id`. A single layer is not enough.
163
176
 
164
177
  ## Do NOT
165
178
 
@@ -178,6 +191,10 @@ Runtime confirmation (e.g. *"reproduce the cross-tenant read against staging"*,
178
191
  - **OWASP Top 10 2021 — A01 Broken Access Control** — canonical failure modes
179
192
  (IDOR, missing function-level checks, forced browsing, metadata tampering).
180
193
  [owasp.org/Top10/A01_2021-Broken_Access_Control/](https://owasp.org/Top10/A01_2021-Broken_Access_Control/)
194
+ - **OWASP API Security Top 10 2023 — API1 BOLA** (Broken Object Level
195
+ Authorization) and **API3 BOPLA** (property-level / mass assignment) — the
196
+ API-layer names for the same object-ownership failures.
197
+ [owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/)
181
198
  - **NIST SP 800-53 AC family** — AC-3 Access Enforcement, AC-6 Least Privilege
182
199
  — rubric for "minimum control" recommendations.
183
200
  [csrc.nist.gov/projects/risk-management/sp800-53-controls](https://csrc.nist.gov/projects/risk-management/sp800-53-controls/release-search#!/800-53)
@@ -228,11 +228,26 @@ When `composer require` or `npm install` fails with conflicts:
228
228
  3. **Use `--dry-run`** first — `composer require vendor/pkg --dry-run`.
229
229
  4. **Never use `--ignore-platform-reqs`** in production — only for investigation.
230
230
 
231
+ ## Post-update malware & behavior audit
232
+
233
+ CVE scanning (above) catches *known-vulnerable* versions. It does **not** catch a **compromised update**: a trusted package with a legitimate history ships a normal-looking version bump that quietly adds data-exfiltration or other harmful behavior. This is how the biggest supply-chain attacks landed — event-stream (wallet stealer as a new transitive dep), ua-parser-js (`preinstall` miner + credential stealer), @solana/web3.js (key exfil hidden inside expected network calls), chalk/debug + 16 (browser crypto-drainer, 2B weekly downloads), the self-propagating Shai-Hulud worm (added a `.github/workflows` + secret-scanning `postinstall`), xz-utils (backdoor in the released tarball, not the git repo). Routine updates slip through because the version looks normal and install runs scripts across the whole transitive tree silently — the exact [`lethal-trifecta-guard`](../../rules/lethal-trifecta-guard.md) egress shape.
234
+
235
+ **Run this audit during/after any add or upgrade, then surface findings to the user and hold pending confirmation** (per [`active-remediation`](../../rules/active-remediation.md) — a live supply-chain risk is surfaced, never silently accepted):
236
+
237
+ 1. **Install without running scripts first** — `npm install --ignore-scripts` (npm v12 defaults to this), `composer install --no-scripts`, `pip install --only-binary :all:` (wheels only — no sdist build code) — so install-time code (the #1 RCE vector) can't run before inspection.
238
+ 2. **Diff the version delta old→new** — the `scripts` block (any newly-added `pre/post/install` hook), the dependency tree (any **new transitive dependency**), and where feasible the published tarball vs the git source (xz hid in the tarball).
239
+ 3. **Capability / behavior diff** — did the new version add a capability its job doesn't need: network egress (`fetch`/`net`/`dns`/`http`), `child_process`/shell, env/secret/credential reads, filesystem writes, obfuscated/minified blobs, a `.github/workflows` file? Use `socket` / `guarddog <eco> scan <pkg>@<ver>` if available; else read the delta.
240
+ 4. **Purpose-vs-behavior legitimacy check** — a Slack/HTTP client legitimately makes network calls; a date/string/color util does not. For each new capability ask: *does the package's stated purpose require this?* A network/secret capability with no purpose justification — or a **new outbound endpoint** even in a package that already uses the network — is high-risk.
241
+ 5. **Provenance + advisory feeds** — `npm audit signatures` (flag a dep that *had* provenance and lost it — a token-theft-publish hallmark); `osv-scanner --lockfile=…` and the GitHub Advisory / Socket / Snyk malware feeds against the newly-resolved versions.
242
+
243
+ **Surface to the user** any: new install script · new transitive dep · new network endpoint / secret read · obfuscated blob · lost provenance · advisory/malware hit — with the version delta + the purpose-vs-behavior verdict, and hold the update until they decide. Never auto-accept a bump that introduced an unexplained capability.
244
+
231
245
  ## Output format
232
246
 
233
247
  1. Updated dependency with version constraint change
234
248
  2. Breaking changes addressed with code modifications
235
249
  3. Test results confirming compatibility
250
+ 4. Post-update audit result: capability delta old→new, purpose-vs-behavior verdict, provenance/advisory status — and any finding surfaced to the user for confirmation
236
251
 
237
252
  ## Auto-trigger keywords
238
253
 
@@ -240,9 +255,14 @@ When `composer require` or `npm install` fails with conflicts:
240
255
  - package update
241
256
  - breaking changes
242
257
  - changelog review
258
+ - malicious package
259
+ - supply chain
260
+ - compromised update
243
261
 
244
262
  ## Gotcha
245
263
 
264
+ - **A trusted package is not a safe version.** Every major supply-chain attack (event-stream, ua-parser-js, chalk/debug, xz) shipped through a *legitimate* package's normal-looking bump — maintainer-authored ≠ safe. Run the post-update behavior audit, not just `audit` for CVEs.
265
+ - **`audit` (CVE) ≠ malware scan.** `npm/composer audit` only knows *published* vulnerabilities; a fresh compromised version has no CVE yet. The capability/behavior diff + purpose-vs-behavior check is what catches zero-hour malware.
246
266
  - Don't upgrade multiple major versions at once — one major version per upgrade cycle.
247
267
  - The model tends to skip reading the CHANGELOG — breaking changes hide in minor releases too.
248
268
  - Always run the full test suite after upgrading, not just the affected tests.
@@ -0,0 +1,104 @@
1
+ ---
2
+ model_tier: medium
3
+ name: frontend-render-security
4
+ description: "Writing/reviewing client-side UI (React/Vue/vanilla) — insecure-render + client-trust gaps AI ships: XSS via innerHTML, client secrets, client-only auth, CORS wildcard, token in localStorage"
5
+ domain: quality
6
+ workspaces:
7
+ - engineering
8
+ packs:
9
+ - engineering-base
10
+ ---
11
+
12
+ # frontend-render-security
13
+
14
+ AI optimizes for the shortest code producing the requested visible behavior and omits the invisible defensive layer — the sanitizer, origin check, server-side gate, security header. Large samples put XSS in a majority of AI frontend code, ~2.74× more than human; every agent in the Tenzai benchmark shipped client-side flaws. High-precision, grep-catchable patterns — stop them at authoring time.
15
+
16
+ ## When to use
17
+
18
+ - Writing/modifying a component, template, or client-side script that renders data, calls an API, handles auth, or reads the URL / `postMessage`.
19
+ - Reviewing an AI-authored frontend diff.
20
+ - Editing `.tsx` / `.jsx` / `.vue` / `.svelte` / `.html` or client-side `.js` / `.ts`.
21
+
22
+ Do NOT use when: change is server-only, CLI, or non-UI — route to `security` / `security-sensitive-stop`.
23
+
24
+ ## The Iron Law
25
+
26
+ ```
27
+ NEVER RENDER NON-CONSTANT INPUT INTO AN HTML/JS SINK WITHOUT ENCODING OR SANITIZING.
28
+ THE CLIENT IS UNTRUSTED — EVERY CLIENT GATE NEEDS A SERVER-SIDE TWIN.
29
+ NO SECRET, PRIVILEGED KEY, OR SESSION TOKEN LIVES IN CLIENT CODE OR localStorage.
30
+ ```
31
+
32
+ ## Procedure
33
+
34
+ 1. Identify the sinks in the diff: HTML/JS render points, API calls, auth/role checks, URL / `postMessage` reads, secret/token usage.
35
+ 2. Run the backstop greps below; for each hit, apply the matching fix from the patterns table.
36
+ 3. For every client-side gate, confirm a server-side twin exists (the client is never the security boundary).
37
+ 4. Emit the per-diff verdict (Output format); surface any unresolved hit to the user.
38
+
39
+ ## The patterns AI ships by default
40
+
41
+ | Pattern | Why dangerous | Do instead |
42
+ |---|---|---|
43
+ | `dangerouslySetInnerHTML` / `v-html` / `.innerHTML =` on non-constant value | Stored/DOM XSS (CWE-79) | render as text, or sanitize (DOMPurify) if HTML required |
44
+ | Secret / API key inline or in `NEXT_PUBLIC_*` / `VITE_*` | Bundler inlines it into shipped JS (CWE-798) | call third party via backend-for-frontend proxy; only truly-public values get a public prefix |
45
+ | Client-side-only auth / role / validation | Bypassable via dev tools or proxy (CWE-602) | client gate = UX only; enforce server-side |
46
+ | `location.*` / query param → HTML or JS sink | DOM XSS (CWE-79) | `textContent`; never pass URL data into an HTML/exec sink |
47
+ | CORS `origin: '*'` (esp. with credentials) | Any site reads authenticated responses (CWE-942) | explicit origin allow-list; never reflect `Origin` on credentialed routes |
48
+ | Token / JWT in `localStorage` / `sessionStorage` | XSS-exfiltratable (CWE-522) | `HttpOnly; Secure; SameSite` cookie |
49
+ | `addEventListener('message', …)` without `event.origin` check | Any embedder drives the handler (CWE-346) | strict-equality `event.origin` vs allow-list first |
50
+ | `redirect` / `next` param → `location` / `router.push` | Open redirect / phishing (CWE-601) | relative-path or allow-listed-host only; reject external / non-http schemes |
51
+ | `eval` / `new Function` / string-`setTimeout` on input | Eval injection / RCE (CWE-95) | a parser or explicit allow-list |
52
+ | `target="_blank"` without `rel="noopener"` | Reverse tabnabbing (CWE-1022) | add `rel="noopener noreferrer"` |
53
+ | Missing CSP; missing alt/label/contrast | No XSS second line of defense; a11y failures | strict CSP (no `unsafe-inline`); alt text, labels, AA contrast |
54
+
55
+ ## Backstop greps
56
+
57
+ Run before committing frontend changes; each returns zero (or every hit is read + justified):
58
+
59
+ ```bash
60
+ # Insecure render + eval sinks
61
+ rg -n 'dangerouslySetInnerHTML|v-html|\.innerHTML\s*=|document\.write\(|\beval\(|new Function\('
62
+ # Client secrets / token storage
63
+ rg -n 'NEXT_PUBLIC_.*(SECRET|KEY|TOKEN|PASSWORD)|VITE_.*(SECRET|KEY)|localStorage\.setItem\([^)]*[Tt]oken'
64
+ # Wildcard CORS + unchecked postMessage + open redirect
65
+ rg -n "origin:\s*['\"]\*['\"]|Access-Control-Allow-Origin.*\*"
66
+ rg -n "addEventListener\(\s*['\"]message['\"]" src/ # then confirm each checks event.origin
67
+ # Unhardened external links
68
+ rg -n 'target=["'\'']_blank["'\'']' src/ # then confirm rel="noopener" present
69
+ ```
70
+
71
+ ## Output format
72
+
73
+ 1. A per-diff verdict listing each pattern class checked + result (`clean` / `hit at file:line → fixed by <change>`).
74
+ 2. The backstop greps run, with results.
75
+ 3. For any client gate added, file:line of its **server-side twin** (or an explicit note the server check already exists + where).
76
+
77
+ ## Gotcha
78
+
79
+ - Modern browsers default `_blank` to `noopener`, but legacy/embedded webviews don't — keep the `rel` for portability.
80
+ - `NEXT_PUBLIC_`/`VITE_` on a *genuinely* public value (a publishable analytics ID) is fine; the violation is a *sensitive* name behind that prefix. Read the name, don't blanket-block the prefix.
81
+ - A sanitizer (DOMPurify) is the fix only when HTML output is actually required; if plain text suffices, render text and skip the dependency.
82
+ - Client-side validation isn't wrong — it's wrong *as the only* enforcement. Keep it for UX; add the server twin.
83
+
84
+ ## Do NOT
85
+
86
+ - Do NOT render user/DB HTML through `dangerouslySetInnerHTML`/`v-html`/`innerHTML` without a sanitizer.
87
+ - Do NOT put a secret, privileged key, or session token in client code or web storage.
88
+ - Do NOT treat a client-side role/auth check as a security boundary.
89
+ - Do NOT set `origin: '*'` on a credentialed endpoint.
90
+ - Do NOT auto-strip a grep hit without reading it — some innerHTML uses are on constant, trusted markup.
91
+
92
+ ## Auto-trigger keywords
93
+
94
+ - frontend security
95
+ - XSS
96
+ - dangerouslySetInnerHTML
97
+ - client-side secret
98
+ - insecure render
99
+
100
+ ## See also
101
+
102
+ - [`ai-code-blindspots`](../ai-code-blindspots/SKILL.md) — the surface→controls checklist that routes here.
103
+ - [`senior-engineering-discipline`](../../rules/senior-engineering-discipline.md) — anchor rule.
104
+ - [`security`](../security/SKILL.md), [`defense-in-depth`](../defense-in-depth/SKILL.md), [`accessibility-auditor`](../accessibility-auditor/SKILL.md), [`secrets-management`](../secrets-management/SKILL.md).
@@ -0,0 +1,52 @@
1
+ {
2
+ "skill": "frontend-render-security",
3
+ "last_eval": "2026-07-09",
4
+ "description": "5 should-trigger + 5 should-not-trigger queries. Should-trigger covers DE + EN phrasings for client-side insecure-render / client-trust patterns (XSS, client secrets, client-only auth, CORS, localStorage tokens). Should-not-trigger covers near-miss neighbours (server-side security, generic checklist, accessibility-only, dependency intake) whose vocabulary overlaps.",
5
+ "queries": [
6
+ {
7
+ "q": "is it safe to render this user content with dangerouslySetInnerHTML?",
8
+ "trigger": true
9
+ },
10
+ {
11
+ "q": "review this Vue component for XSS and any secrets in the bundle",
12
+ "trigger": true
13
+ },
14
+ {
15
+ "q": "I'm storing the JWT in localStorage — is that ok?",
16
+ "trigger": true
17
+ },
18
+ {
19
+ "q": "prüf mein Frontend auf unsichere Render-Muster und Client-Secrets",
20
+ "trigger": true
21
+ },
22
+ {
23
+ "q": "my CORS is set to origin '*' with credentials — problem?",
24
+ "trigger": true
25
+ },
26
+ {
27
+ "q": "review this API controller's authorization logic",
28
+ "trigger": false,
29
+ "note": "server-side authz → authz-review / security-sensitive-stop"
30
+ },
31
+ {
32
+ "q": "what invisible controls did I omit across this whole diff?",
33
+ "trigger": false,
34
+ "note": "cross-surface checklist → ai-code-blindspots"
35
+ },
36
+ {
37
+ "q": "make this form accessible — keyboard nav and contrast",
38
+ "trigger": false,
39
+ "note": "accessibility only → accessibility-auditor"
40
+ },
41
+ {
42
+ "q": "is this frontend npm dependency real and safe to install?",
43
+ "trigger": false,
44
+ "note": "dependency intake → supply-chain-intake"
45
+ },
46
+ {
47
+ "q": "help me center this div with flexbox",
48
+ "trigger": false,
49
+ "note": "layout/styling — no security surface"
50
+ }
51
+ ]
52
+ }
@@ -61,6 +61,13 @@ in [`.agent-src.uncondensed/templates/roadmaps.md`](../../templates/roadmaps.md)
61
61
  Read it before authoring. Do not restate its rules in the roadmap
62
62
  body — link the template if a phase needs to override one.
63
63
 
64
+ **Naming:** a lone roadmap is `road-to-<slug>.md`. When you create **≥2
65
+ related** roadmaps in one pass (siblings split from one body of work, or
66
+ a follow-up chain), give them a shared `road-to-<family>-<part>.md`
67
+ prefix so relatedness is visible and the dashboard groups them — pick
68
+ the `<family>` slug up front (template rule 21). Follow-ups additionally
69
+ carry `parent_roadmap:` (rule 17).
70
+
64
71
  ### 2. Pick complexity tier honestly
65
72
 
66
73
  Default `lightweight` (≤ 6 phases, ≤ 600 lines). Only use
@@ -224,6 +224,10 @@ record it in the commit message (`Eval stub: deferred`). Peer examples
224
224
  for the expected format: `php-coder/evals/triggers.json`,
225
225
  `eloquent/evals/triggers.json`, `skill-writing/evals/triggers.json`.
226
226
 
227
+ Presence CI-enforced for new skills: `check_trigger_eval_presence` fails
228
+ any skill outside shrink-only grandfather allowlist lacking
229
+ `evals/triggers.json` — *skip* defers query quality, never the file.
230
+
227
231
  Rules / commands / guidelines do **not** get eval stubs — only skills
228
232
  route through the top-level catalogue.
229
233