@ethlete/agent-rules 0.1.0-next.2 → 0.1.0-next.21

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 (178) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/README.md +350 -29
  3. package/THIRD-PARTY-LICENSES.md +35 -0
  4. package/content/git-hooks/post-checkout.sh +10 -0
  5. package/content/git-hooks/pre-push.sh +5 -0
  6. package/content/hooks/context-warning.py +766 -108
  7. package/content/hooks/subagent-model-policy.py +193 -0
  8. package/content/output-styles/ste-clarity.md +131 -0
  9. package/content/rules/app-styling.md +59 -0
  10. package/content/rules/comments.md +53 -20
  11. package/content/rules/lint-and-format.md +6 -5
  12. package/content/rules/nx-layout.md +47 -0
  13. package/content/rules/reactive-state.md +1 -1
  14. package/content/rules/styling.md +9 -7
  15. package/content/rules/subagent-models.md +29 -0
  16. package/content/skills/angular-patterns/SKILL.md +30 -7
  17. package/content/skills/api-source/SKILL.md +130 -0
  18. package/content/skills/app-testing/SKILL.md +141 -0
  19. package/content/skills/codex-subagent/SKILL.md +90 -0
  20. package/content/skills/codex-subagent/codex-agent.mjs +229 -0
  21. package/content/skills/design-exploration/SKILL.md +205 -0
  22. package/content/skills/design-exploration/check-story.mjs +139 -0
  23. package/content/skills/design-exploration/shoot-template.mjs +61 -0
  24. package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
  25. package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  26. package/content/skills/domain-modeling/SKILL.md +80 -0
  27. package/content/skills/figma-export/SKILL.md +191 -0
  28. package/content/skills/figma-export/dump-figma-layers.py +83 -0
  29. package/content/skills/figma-export/dump-figma-svg.py +235 -0
  30. package/content/skills/figma-export/measure-template.mjs +87 -0
  31. package/content/skills/git-commit/SKILL.md +6 -7
  32. package/content/skills/git-flow/SKILL.md +87 -0
  33. package/content/skills/grill-with-docs/SKILL.md +13 -0
  34. package/content/skills/grilling/SKILL.md +34 -0
  35. package/content/skills/handoff/SKILL.md +74 -4
  36. package/content/skills/query/SKILL.md +165 -64
  37. package/content/skills/rxjs-signals/SKILL.md +31 -4
  38. package/content/skills/sdk-docs/SKILL.md +50 -14
  39. package/content/skills/sdk-local-build/SKILL.md +30 -10
  40. package/content/skills/sdk-local-build/sdk-local-baseline.mjs +64 -0
  41. package/content/skills/sdk-source/SKILL.md +49 -21
  42. package/content/skills/sdk-update/SKILL.md +96 -0
  43. package/content/skills/story-styling/SKILL.md +10 -8
  44. package/content/skills/styleguide/SKILL.md +12 -7
  45. package/content/skills/styleguide/assets.md +54 -0
  46. package/content/skills/styleguide/changesets.md +17 -0
  47. package/content/skills/styleguide/file-structure.md +73 -0
  48. package/content/skills/styleguide/lint-rule-lookup.md +51 -0
  49. package/content/skills/styleguide/storybook-structure.md +25 -0
  50. package/content/skills/theming/SKILL.md +41 -28
  51. package/content/skills/timetrack/SKILL.md +397 -0
  52. package/content/skills/verify-in-app/SKILL.md +107 -0
  53. package/migrations/app-styling-utilities.md +63 -0
  54. package/migrations/list-state-query-form.md +20 -0
  55. package/migrations/nx-layout.md +29 -0
  56. package/migrations/sdk-components-over-hand-built-ui.md +27 -0
  57. package/migrations/search-query-field.md +52 -0
  58. package/migrations.json +48 -0
  59. package/package.json +16 -2
  60. package/src/index.js +63 -28
  61. package/src/index.js.map +1 -1
  62. package/src/lib/cli-args.d.ts +9 -0
  63. package/src/lib/cli-args.js +48 -0
  64. package/src/lib/cli-args.js.map +1 -0
  65. package/src/lib/commitlint.d.ts +10 -0
  66. package/src/lib/commitlint.js +51 -0
  67. package/src/lib/commitlint.js.map +1 -0
  68. package/src/lib/config.d.ts +38 -4
  69. package/src/lib/config.js +84 -8
  70. package/src/lib/config.js.map +1 -1
  71. package/src/lib/filter.d.ts +2 -0
  72. package/src/lib/filter.js +4 -4
  73. package/src/lib/filter.js.map +1 -1
  74. package/src/lib/frontmatter.d.ts +2 -0
  75. package/src/lib/frontmatter.js +33 -5
  76. package/src/lib/frontmatter.js.map +1 -1
  77. package/src/lib/git-flow/build.d.ts +35 -0
  78. package/src/lib/git-flow/build.js +24 -0
  79. package/src/lib/git-flow/build.js.map +1 -0
  80. package/src/lib/git-flow/config.d.ts +59 -0
  81. package/src/lib/git-flow/config.js +50 -0
  82. package/src/lib/git-flow/config.js.map +1 -0
  83. package/src/lib/git-flow/index.d.ts +6 -0
  84. package/src/lib/git-flow/index.js +10 -0
  85. package/src/lib/git-flow/index.js.map +1 -0
  86. package/src/lib/git-flow/parse.d.ts +49 -0
  87. package/src/lib/git-flow/parse.js +300 -0
  88. package/src/lib/git-flow/parse.js.map +1 -0
  89. package/src/lib/git-flow/rename.d.ts +24 -0
  90. package/src/lib/git-flow/rename.js +70 -0
  91. package/src/lib/git-flow/rename.js.map +1 -0
  92. package/src/lib/git-flow/start.d.ts +49 -0
  93. package/src/lib/git-flow/start.js +57 -0
  94. package/src/lib/git-flow/start.js.map +1 -0
  95. package/src/lib/git-flow/validate.d.ts +34 -0
  96. package/src/lib/git-flow/validate.js +72 -0
  97. package/src/lib/git-flow/validate.js.map +1 -0
  98. package/src/lib/git-flow-command.d.ts +4 -0
  99. package/src/lib/git-flow-command.js +157 -0
  100. package/src/lib/git-flow-command.js.map +1 -0
  101. package/src/lib/git-flow-repair.d.ts +17 -0
  102. package/src/lib/git-flow-repair.js +157 -0
  103. package/src/lib/git-flow-repair.js.map +1 -0
  104. package/src/lib/git-flow-start.d.ts +20 -0
  105. package/src/lib/git-flow-start.js +132 -0
  106. package/src/lib/git-flow-start.js.map +1 -0
  107. package/src/lib/git.d.ts +64 -0
  108. package/src/lib/git.js +131 -0
  109. package/src/lib/git.js.map +1 -0
  110. package/src/lib/gitlab.d.ts +35 -0
  111. package/src/lib/gitlab.js +108 -0
  112. package/src/lib/gitlab.js.map +1 -0
  113. package/src/lib/index.d.ts +2 -0
  114. package/src/lib/index.js +2 -0
  115. package/src/lib/index.js.map +1 -1
  116. package/src/lib/migrate.js +1 -1
  117. package/src/lib/migrate.js.map +1 -1
  118. package/src/lib/output-style-command.d.ts +3 -0
  119. package/src/lib/output-style-command.js +69 -0
  120. package/src/lib/output-style-command.js.map +1 -0
  121. package/src/lib/output-style.d.ts +38 -0
  122. package/src/lib/output-style.js +134 -0
  123. package/src/lib/output-style.js.map +1 -0
  124. package/src/lib/owned-paths.js +27 -3
  125. package/src/lib/owned-paths.js.map +1 -1
  126. package/src/lib/package-runner.d.ts +3 -0
  127. package/src/lib/package-runner.js +69 -0
  128. package/src/lib/package-runner.js.map +1 -0
  129. package/src/lib/plain-text.d.ts +9 -0
  130. package/src/lib/plain-text.js +22 -0
  131. package/src/lib/plain-text.js.map +1 -0
  132. package/src/lib/plan.d.ts +6 -6
  133. package/src/lib/plan.js +147 -40
  134. package/src/lib/plan.js.map +1 -1
  135. package/src/lib/prompt.d.ts +8 -0
  136. package/src/lib/prompt.js +27 -0
  137. package/src/lib/prompt.js.map +1 -0
  138. package/src/lib/render.d.ts +24 -2
  139. package/src/lib/render.js +39 -10
  140. package/src/lib/render.js.map +1 -1
  141. package/src/lib/sync.d.ts +0 -1
  142. package/src/lib/sync.js +23 -15
  143. package/src/lib/sync.js.map +1 -1
  144. package/src/lib/targets/agents-skills.js +1 -1
  145. package/src/lib/targets/agents-skills.js.map +1 -1
  146. package/src/lib/targets/claude-hooks.d.ts +1 -23
  147. package/src/lib/targets/claude-hooks.js +17 -85
  148. package/src/lib/targets/claude-hooks.js.map +1 -1
  149. package/src/lib/targets/claude.js +1 -2
  150. package/src/lib/targets/claude.js.map +1 -1
  151. package/src/lib/targets/codex-hooks.d.ts +12 -0
  152. package/src/lib/targets/codex-hooks.js +34 -0
  153. package/src/lib/targets/codex-hooks.js.map +1 -0
  154. package/src/lib/targets/codex.js +4 -4
  155. package/src/lib/targets/codex.js.map +1 -1
  156. package/src/lib/targets/copilot.js +3 -3
  157. package/src/lib/targets/copilot.js.map +1 -1
  158. package/src/lib/targets/cursor.js +2 -2
  159. package/src/lib/targets/cursor.js.map +1 -1
  160. package/src/lib/targets/git-hooks.d.ts +24 -0
  161. package/src/lib/targets/git-hooks.js +71 -0
  162. package/src/lib/targets/git-hooks.js.map +1 -0
  163. package/src/lib/targets/hooks-shared.d.ts +58 -0
  164. package/src/lib/targets/hooks-shared.js +128 -0
  165. package/src/lib/targets/hooks-shared.js.map +1 -0
  166. package/src/lib/targets/shared.d.ts +1 -9
  167. package/src/lib/targets/shared.js +4 -8
  168. package/src/lib/targets/shared.js.map +1 -1
  169. package/src/lib/timetrack-command.d.ts +11 -0
  170. package/src/lib/timetrack-command.js +1055 -0
  171. package/src/lib/timetrack-command.js.map +1 -0
  172. package/src/lib/timetrack-snapshot.d.ts +55 -0
  173. package/src/lib/timetrack-snapshot.js +120 -0
  174. package/src/lib/timetrack-snapshot.js.map +1 -0
  175. package/src/lib/timetrack.d.ts +561 -0
  176. package/src/lib/timetrack.js +271 -0
  177. package/src/lib/timetrack.js.map +1 -0
  178. package/content/skills/styleguide/STYLEGUIDE.md +0 -520
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env python3
2
+ """PreToolUse hook: make a subagent's model an explicit choice.
3
+
4
+ A `Task` call that passes no `model` runs the subagent on the parent session's
5
+ model. A session led by an expensive model therefore spends that model on every
6
+ grep and every file read it delegates, which burns a usage limit in minutes for
7
+ work a small model does just as well.
8
+
9
+ So this hook gates the tool call rather than only advising:
10
+
11
+ * No `model` at all - denied, with the model table returned to the agent. The
12
+ agent then repeats the call with a model, so the cost of the mistake is one
13
+ tool call, not a whole subagent run.
14
+ * `model: fable` - the user is asked. Fable is the right pick for planning,
15
+ design, cross-cutting review and leading other subagents, and the wrong pick
16
+ for the twentieth mechanical lookup; only the user knows which this is.
17
+ * Any other model - allowed silently.
18
+
19
+ Two cases are left alone, because the tool's `model` would change nothing:
20
+
21
+ * `subagent_type: fork`, which always inherits the parent model.
22
+ * An agent type whose own definition under `.claude/agents/` sets a model.
23
+
24
+ Can be disabled per machine via a gitignored ethlete-agents.config.local.json at
25
+ the repo root: {"disableHooks": true} or {"disableHooks": ["subagent-model-policy"]}.
26
+
27
+ Fail-safe: any error exits 0 with no output - the hook must never block work
28
+ because it could not read its own input.
29
+ """
30
+
31
+ import json
32
+ import os
33
+ import sys
34
+
35
+ HOOK_NAME = "subagent-model-policy"
36
+ LOCAL_CONFIG_FILE = "ethlete-agents.config.local.json"
37
+
38
+ # The tool that spawns a subagent, under both names it has carried.
39
+ SUBAGENT_TOOLS = ("Task", "Agent")
40
+
41
+ POLICY = """A subagent's model is a choice, not an inheritance. This call passes no `model`, so the subagent \
42
+ would run on the model leading this session - which is how one expensive model ends up doing every small job. \
43
+ Call the tool again with `model` set:
44
+
45
+ - `haiku` - lookups and small, fully specified jobs: grep, find, read and summarize files or a CI log, run a command and report, a mechanical edit in named files.
46
+ - `sonnet` - scoped work with a clear target: a fix in a named file, a spec, a docs page, a lint cleanup, \
47
+ research that needs reasoning. Sonnet 5.5 is close to opus here and costs less.
48
+ - `opus` - hard work: a bug with no known cause, a change across many files, a review of a diff, and any task \
49
+ sonnet did not finish.
50
+ - `fable` - judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most \
51
+ expensive of the four, so choosing it asks the user first.
52
+
53
+ Effort follows the prompt, not a parameter: scope the prompt to one question, name what "done" is, and say \
54
+ "keep it brief" for a lookup. An agent type defined under `.claude/agents/` carries its own model and reasoning \
55
+ effort, so a call that names one needs no `model` of its own."""
56
+
57
+ FABLE_REASON = """This subagent would run on fable, the most expensive model. Approve it where the task is \
58
+ judgment-heavy - planning, design, cross-cutting review, or leading other subagents. Deny it for implementation \
59
+ (`model: "sonnet"` or `"opus"`) or for a lookup or a small, fully specified job (`model: "haiku"`), then repeat the call with that model."""
60
+
61
+
62
+ def agent_name(argv):
63
+ """The --agent value, defaulting to claude — older registrations pass no flag."""
64
+ for index, arg in enumerate(argv):
65
+ if arg == "--agent" and index + 1 < len(argv):
66
+ return argv[index + 1]
67
+ if arg.startswith("--agent="):
68
+ return arg.split("=", 1)[1]
69
+ return "claude"
70
+
71
+
72
+ def repo_root(data):
73
+ """Repo root: the env var the agent sets, else the script's own location, else the cwd.
74
+
75
+ The script always lives at <root>/.<agent>/hooks/ethlete/subagent-model-policy.py, so
76
+ walking four levels up works for any agent that has no project-dir variable of its own.
77
+ """
78
+ for variable in ("CLAUDE_PROJECT_DIR", "CODEX_PROJECT_DIR"):
79
+ value = os.environ.get(variable)
80
+ if value:
81
+ return value
82
+ here = os.path.abspath(__file__)
83
+ derived = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(here))))
84
+ if os.path.isfile(os.path.join(derived, LOCAL_CONFIG_FILE)):
85
+ return derived
86
+ cwd = data.get("cwd")
87
+ return cwd if isinstance(cwd, str) and cwd else derived
88
+
89
+
90
+ def load_local_config(root):
91
+ """Parsed ethlete-agents.config.local.json at the repo root, or {} if missing/unreadable."""
92
+ if not root:
93
+ return {}
94
+ try:
95
+ with open(os.path.join(root, LOCAL_CONFIG_FILE), encoding="utf-8") as f:
96
+ config = json.load(f)
97
+ except (OSError, ValueError, AttributeError):
98
+ return {}
99
+ return config if isinstance(config, dict) else {}
100
+
101
+
102
+ def disabled_locally(config):
103
+ """True when the local config disables this hook (or all hooks) on this machine."""
104
+ disabled = config.get("disableHooks")
105
+ return disabled is True or (isinstance(disabled, list) and HOOK_NAME in disabled)
106
+
107
+
108
+ def definition_sets_model(root, subagent_type):
109
+ """True when a project or user agent definition of that name declares its own model."""
110
+ if not subagent_type:
111
+ return False
112
+ file_name = f"{subagent_type.split(':')[-1]}.md".lower()
113
+ candidates = []
114
+ for directory in (
115
+ os.path.join(root or ".", ".claude", "agents"),
116
+ os.path.join(os.path.expanduser("~"), ".claude", "agents"),
117
+ ):
118
+ try:
119
+ entries = os.listdir(directory)
120
+ except OSError:
121
+ continue
122
+ candidates += [os.path.join(directory, entry) for entry in entries if entry.lower() == file_name]
123
+ for path in candidates:
124
+ try:
125
+ with open(path, encoding="utf-8") as f:
126
+ head = f.read(4096)
127
+ except OSError:
128
+ continue
129
+ for line in head.split("\n"):
130
+ key, separator, value = line.partition(":")
131
+ if separator and key.strip() == "model" and value.strip():
132
+ return True
133
+ return False
134
+
135
+
136
+ def decision_for(tool_input, root):
137
+ """The (decision, reason) this call needs, or None when it may run untouched."""
138
+ subagent_type = str(tool_input.get("subagent_type") or "").strip().lower()
139
+ model = str(tool_input.get("model") or "").strip().lower()
140
+
141
+ if "fable" in model:
142
+ return "ask", FABLE_REASON
143
+ if model:
144
+ return None
145
+ if subagent_type == "fork" or definition_sets_model(root, subagent_type):
146
+ return None
147
+ return "deny", POLICY
148
+
149
+
150
+ def main():
151
+ if agent_name(sys.argv[1:]) != "claude":
152
+ return
153
+
154
+ data = json.load(sys.stdin)
155
+
156
+ if data.get("tool_name") not in SUBAGENT_TOOLS:
157
+ return
158
+
159
+ tool_input = data.get("tool_input")
160
+
161
+ if not isinstance(tool_input, dict):
162
+ return
163
+
164
+ root = repo_root(data)
165
+
166
+ if disabled_locally(load_local_config(root)):
167
+ return
168
+
169
+ outcome = decision_for(tool_input, root)
170
+
171
+ if not outcome:
172
+ return
173
+
174
+ decision, reason = outcome
175
+ print(
176
+ json.dumps(
177
+ {
178
+ "hookSpecificOutput": {
179
+ "hookEventName": "PreToolUse",
180
+ "permissionDecision": decision,
181
+ "permissionDecisionReason": reason,
182
+ }
183
+ }
184
+ )
185
+ )
186
+
187
+
188
+ if __name__ == "__main__":
189
+ try:
190
+ main()
191
+ except Exception:
192
+ pass
193
+ sys.exit(0)
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: ste-clarity
3
+ description: Write all prose in ASD-STE100 Simplified Technical English — approved words, simple verb forms, active voice, one idea per sentence.
4
+ keep-coding-instructions: true
5
+ ---
6
+
7
+ # ASD-STE100 output style
8
+
9
+ Write every sentence you show the user in Simplified Technical English, as specified
10
+ by ASD-STE100. Clarity comes first. Never sacrifice technical accuracy for brevity.
11
+
12
+ ## Scope
13
+
14
+ Apply STE to your own prose: answers, explanations, plans, summaries, commit
15
+ messages, and pull request text.
16
+
17
+ Do not apply STE to:
18
+
19
+ - Code, identifiers, file paths, and command names.
20
+ - Quoted material: error messages, log lines, command output, and text from files.
21
+ Copy these exactly. Never rewrite a quote to make it simpler.
22
+ - Text the user asks you to write in a different style.
23
+
24
+ Markdown is still available. Use code blocks, and use `file.ts:42` references.
25
+
26
+ ## Words
27
+
28
+ 1. Use the most common, shortest word that has one clear meaning.
29
+ 2. Use one word for one meaning. Do not use synonyms for the same thing. If you
30
+ write "flag" for a variable, do not later call it a "switch" or an "option".
31
+ 3. Use one meaning for one word. Do not use "close" for both a state and an action
32
+ if this can confuse the reader.
33
+ 4. Technical names are permitted. Use the exact name from the code or the domain.
34
+ 5. Do not use slang, idioms, or figures of speech. "The build blew up" is wrong.
35
+ "The build failed" is correct.
36
+ 6. Do not use "etc." Complete the list, or write "for example" and give two items.
37
+ 7. Do not use a noun as a verb. Write "make a request", not "request it", when
38
+ "request" is a name in the code.
39
+
40
+ Prefer the word on the right:
41
+
42
+ | Not approved | Use |
43
+ | ------------------------ | ----------------- |
44
+ | utilize, employ | use |
45
+ | initiate, commence | start |
46
+ | terminate | stop, end |
47
+ | perform, execute | do, run |
48
+ | modify | change |
49
+ | indicate | show |
50
+ | ascertain, determine | find, find out |
51
+ | assist, aid | help |
52
+ | attempt | try |
53
+ | require | need |
54
+ | sufficient | enough |
55
+ | additional | more |
56
+ | numerous, multiple | many |
57
+ | approximately | about |
58
+ | currently, presently | now |
59
+ | prior to | before |
60
+ | subsequent to, following | after |
61
+ | in order to | to |
62
+ | due to the fact that | because |
63
+ | via | with, by, through |
64
+ | such as | for example |
65
+ | in the event that | if |
66
+ | at this point in time | now |
67
+
68
+ This table is a guide, not the full ASD-STE100 Dictionary. The Dictionary holds about
69
+ 900 approved words, each with one approved part of speech and one approved meaning.
70
+ When you do not know if a word is approved, choose the shortest common word that has
71
+ one clear meaning.
72
+
73
+ ## Verbs
74
+
75
+ 1. Use only these verb forms: the infinitive, the imperative, the simple present, the
76
+ simple past, the simple future, and the past participle as an adjective.
77
+ 2. Do not use complex tenses. Write "I changed the config", not "I have changed the
78
+ config".
79
+ 3. Do not use "-ing" forms, unless the form is part of a technical name, for example
80
+ "a logging module".
81
+ 4. Use the active voice. Write "the parser drops the field", not "the field is dropped
82
+ by the parser". Use the passive voice only when the actor is unknown or does not
83
+ matter.
84
+ 5. Do not leave out a verb to make a sentence shorter.
85
+
86
+ ## Noun phrases
87
+
88
+ 1. Do not build a cluster of more than three nouns. Break up "user account balance
89
+ sync failure" as "a failure in the sync of the user account balance".
90
+ 2. Add articles ("a", "the") where they help the reader.
91
+
92
+ ## Sentences
93
+
94
+ 1. Instructions: 20 words maximum.
95
+ 2. Descriptions: 25 words maximum.
96
+ 3. One idea per sentence. One instruction per sentence.
97
+ 4. A paragraph holds 6 sentences maximum, and covers one topic.
98
+ 5. Put a condition at the start of the sentence, before the instruction. Write "If the
99
+ sync fails, restart the server".
100
+ 6. Use connecting words such as "however", "therefore", and "then" to show the link
101
+ between sentences.
102
+ 7. Use a vertical list when the content has more than three parallel parts. Do not use
103
+ a list to give structure to a single idea.
104
+
105
+ ## Procedures
106
+
107
+ 1. Use the imperative for each step. Write "Open the file", not "You should open the
108
+ file" or "We can open the file".
109
+ 2. Number the steps in the order the user must do them.
110
+ 3. Give the result after the action, if the result is not obvious.
111
+
112
+ ## Warnings
113
+
114
+ 1. Put a warning or a caution before the step it applies to, never after.
115
+ 2. Start the warning with a clear command. Write "Do not run this on the production
116
+ database. The command deletes all rows."
117
+ 3. State the consequence in a separate, simple sentence.
118
+
119
+ ## Punctuation
120
+
121
+ 1. Do not use a slash to mean "and" or "or". Write "the date or the payee".
122
+ 2. Do not use parentheses for information the reader needs. Put it in its own sentence.
123
+ 3. Do not use a dash to join two thoughts. Use two sentences.
124
+ 4. Use a hyphen only to make a compound word clear.
125
+
126
+ ## When STE and accuracy conflict
127
+
128
+ Accuracy wins. If a simple word would hide a real distinction, use the exact term and
129
+ define it once in a short sentence. Never simplify a claim about what the code does.
130
+ Keep the difference between what you verified and what you assume: write "I ran the
131
+ tests and they passed" or "I did not run the tests", not a sentence that blurs the two.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: app-styling
3
+ description: App components are styled with Tailwind utilities in their templates; app CSS stays out of @layer components; the SDK needs a 10px rem root; hardcoded colours are never primary values.
4
+ kind: rule
5
+ scope: consumer
6
+ requires: ['@ethlete/core']
7
+ ---
8
+
9
+ ## Styling
10
+
11
+ Every component in this workspace (views, pages, shells, shared UI in `libs/`) is laid out
12
+ and styled with **Tailwind utility classes in its template**, including the colour utilities
13
+ the app's theme provides: the generated SDK theme utilities (`bg-et-surface-bg`,
14
+ `border-et-surface-border`, `text-et-<theme>`) where the app generates them, or the palette its
15
+ Tailwind config defines. Do not
16
+ invent a BEM class system, and do not write a `.css` file for layout, spacing or
17
+ typography a utility expresses. Every app component sets `encapsulation: ViewEncapsulation.None`,
18
+ as the `require-view-encapsulation-none` lint rule requires. Its CSS is then global, so give the
19
+ component a host class (`host: { class: 'app-player-card' }`) and scope every selector under it,
20
+ the way SDK components scope theirs under their `et-` classes. Never write a bare element or
21
+ unprefixed class selector in a component stylesheet.
22
+
23
+ In an existing repo, apply this to the component you are editing, not to the rest of the app. A
24
+ legacy component that relies on emulated encapsulation keeps it until its CSS is scoped under a
25
+ host class: without that scope, `ViewEncapsulation.None` leaks its CSS into every view.
26
+
27
+ Write CSS only for what utilities cannot express. With Tailwind 4, keep that CSS **unlayered**
28
+ or in `@layer utilities` — never in `@layer components`, and that includes the global
29
+ stylesheet. SDK component styles are injected into `@layer components` at runtime, after your
30
+ stylesheet, so an app rule in the same layer ties on layer and loses on source order. With
31
+ Tailwind 3, `@layer` is a Tailwind directive that emits unlayered CSS, so app CSS already beats
32
+ the SDK's layered styles.
33
+
34
+ To change an SDK component's look, set its `--et-*` tokens first (documented per
35
+ component), then use a utility class or unlayered CSS on the element — see "Overriding
36
+ component styles" in the docs site's components overview.
37
+
38
+ The SDK sizes everything in `rem` on a **10px root**: the app's global stylesheet must set
39
+ `html { font-size: 62.5%; }`. Without it every SDK control renders 1.6× too large. With it,
40
+ `1rem` is `10px`, so the app's own `rem` values stay easy to read.
41
+
42
+ The 10px root also shrinks every rem-based Tailwind scale to 62.5% of its nominal size (1.6×
43
+ smaller): `p-4` is 10px instead of 16px, and `max-w-3xl` is 480px instead of 768px. Keep the
44
+ spacing scale on its 4px step: with Tailwind 4, set `--spacing: 0.4rem` in the app's `@theme`,
45
+ as the SDK's own Storybook does; with Tailwind 3, redefine `theme.spacing` in
46
+ `tailwind.config`. In an app that already uses utilities without it, the change rescales every
47
+ view: tell the user rather than make it. Redefine any other rem scale the app uses (text,
48
+ container, radius) the same way, or use px where a size matters. Breakpoints are not affected:
49
+ a media query's `rem` ignores the root font size.
50
+
51
+ **Never use a hardcoded colour as the primary value.** Backgrounds, text, borders and
52
+ interaction states resolve from the app's theme: the SDK surface and colour tokens
53
+ (`--et-surface-*-solid`, `--et-theme-color-*`) and their generated utilities, or the palette
54
+ the app's Tailwind config defines. Never write an arbitrary colour value such as
55
+ `bg-[#1a1a1a]` in a template. A static fallback inside `var(--token, <fallback>)` is
56
+ permitted, but not required.
57
+
58
+ Theme **names** (`brand`, `danger`, `dark-elevated`, …) are registered by this app; the SDK
59
+ ships none. Semantic colours resolve by theme `type` (e.g. `injectSemanticColorTheme('error')`).
@@ -1,31 +1,64 @@
1
1
  ---
2
2
  name: comments
3
- description: Write comments for the next reader of the file, not for the reviewer of your change.
3
+ description: Comments are an allowlist of four cases. Everything else gets deleted before the change is done.
4
4
  kind: rule
5
5
  scope: both
6
6
  ---
7
7
 
8
- ## Comments: write for the next reader of this file, not for the reviewer of your change
8
+ ## Comments: almost none, and never for the reviewer of your change
9
9
 
10
- A comment earns its place by telling someone **using or editing this code** something the
11
- code cannot. Explaining _why the change was made_ is not that — it belongs in the commit
12
- message, the changeset, or the docs.
10
+ **Write no comment unless it fits one of the four cases below.** This is an allowlist, not a
11
+ set of tips. Anything outside it gets **deleted** before you call the change done — not
12
+ softened, not shortened. Code that needs prose to be understood needs a better name, a
13
+ smaller function, or a type; fix that instead of narrating it.
13
14
 
14
- Do **not** leave behind:
15
+ ### The only comments allowed
15
16
 
16
- - **Rationale for a mechanical choice.** `Record<Size, X>` with literal keys, a `@__PURE__`
17
- annotation, a factory instead of a literal, a helper moved to another file — the type,
18
- the annotation and the import already say what happens.
19
- - **Migration narration.** "moved here from X", "used to be a tuple", "so Y no longer pulls Z".
20
- Git knows. A reader six months from now does not care.
21
- - **The same explanation repeated per call site.** If a pattern needs explaining, explain it
22
- once where the pattern is defined (the helper's JSDoc, the lint rule's message, the guide)
23
- and let every use site stay silent.
24
- - **Restating the code.** `// increment the counter` above `counter++`.
17
+ 1. **An ordering or timing constraint** a reasonable edit would break. Say what breaks.
18
+ 2. **An invariant the types cannot express**, that a caller or a future edit could violate.
19
+ 3. **A workaround**, naming its concrete cause (browser bug, upstream issue, framework
20
+ limitation) and linking it where a link exists, so the next reader can tell when it may go.
21
+ 4. **Public API JSDoc** — what it does and how to call it, on something a lib actually
22
+ exports. One or two sentences. Not internals, not history, not why it is shaped that way.
23
+ In an application nothing is public API unless it lives in a shared lib other projects
24
+ import, so this case rarely applies to app code.
25
25
 
26
- Do keep: non-obvious behaviour and ordering constraints, a real invariant a future edit could
27
- break, a workaround with the reason it exists, and public API JSDoc (what it does and how to
28
- use it — not why it is shaped that way).
26
+ Nothing else qualifies. Not "this is subtle", not "worth noting", not a heading over a group
27
+ of members, not a summary of the function underneath it.
29
28
 
30
- When you catch yourself writing "because", check whether the sentence is aimed at the reviewer
31
- of your diff. If it is, cut it.
29
+ ### The test each one still has to pass
30
+
31
+ Delete it unless **both** are true:
32
+
33
+ - a competent reader who never sees your diff would be **surprised** without it, and
34
+ - a future edit could **break something** that this sentence is the only warning about.
35
+
36
+ Unsure counts as no. A missing comment costs a minute of reading; a stale one misleads for
37
+ years.
38
+
39
+ ### Always delete
40
+
41
+ - **Restating the code** — `// increment the counter` over `counter++`; a JSDoc on `size` that
42
+ says nothing beyond "the size of the button".
43
+ - **Section headers and dividers** — `// --- Inputs ---`, `// Helpers`, `// Public API`.
44
+ - **Rationale for a mechanical choice** — `Record<Size, X>` with literal keys, a `@__PURE__`
45
+ annotation, a factory instead of a literal, a helper moved into its own file. The type, the
46
+ annotation and the import already say what happens.
47
+ - **Migration narration** — "moved here from X", "used to be a tuple", "so Y no longer pulls
48
+ Z", "renamed for clarity", "see ADR-0012". Git and the ADR index know; the next reader does
49
+ not care.
50
+ - **The same explanation at every call site.** Explain a pattern once where it is defined (the
51
+ helper's JSDoc, the lint rule's message, the guide) and let every use site stay silent.
52
+ - **Commented-out code.**
53
+ - **`TODO`/`FIXME` without an issue link.** Fix it now or leave nothing.
54
+ - **Hedging and meta** — "note that", "for clarity", "just in case", "this is cleaner", "we
55
+ could also…".
56
+
57
+ ### Before you call the change done
58
+
59
+ Re-read every comment in your diff and cut the ones that are not one of the four. Then fix or
60
+ delete any existing comment your change made wrong — one describing behaviour that no longer
61
+ exists is worse than none.
62
+
63
+ Two signals you have already over-commented: you wrote the word "because", or the diff adds
64
+ more than a handful of comments. Both mean go back and cut.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lint-and-format
3
- description: Run lint with --fix before fixing anything by hand, and format every edited file.
3
+ description: Run lint with --fix before fixing anything by hand, and format every edited source file Prettier supports.
4
4
  kind: rule
5
5
  scope: both
6
6
  vars: [lintCommand, lintFixCommand, formatCommand]
@@ -12,14 +12,15 @@ Run lint with `--fix` — most styleguide rules in `@ethlete/eslint-plugin` ship
12
12
  so let them do the work before correcting anything by hand:
13
13
 
14
14
  ```bash
15
- {%lintFixCommand%} # auto-fixes first (case, ordering, $ suffix, metadata, …)
15
+ {%lintFixCommand%} # auto-fixes first (case, ordering, metadata, …); rename to the $ suffix by hand
16
16
  {%lintCommand%} # then re-run to see what needs a manual fix
17
17
  ```
18
18
 
19
- For the judgment calls lint cannot enforce — signals vs RxJS, templates, lifecycle and DI
20
- patterns — see {%skill:styleguide%}.
19
+ For judgment calls lint cannot enforce, load the repository's focused guidance for the
20
+ code you are changing.
21
21
 
22
- After editing any file, format it before wrapping up:
22
+ Format every edited Prettier-supported source file before wrapping up. Do not send binary,
23
+ generated, or unsupported files to this command:
23
24
 
24
25
  ```bash
25
26
  {%formatCommand%}
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: nx-layout
3
+ description: Nx workspace layout - thin apps, feature code in libs grouped by kind, path-mirroring aliases, scope tags.
4
+ kind: rule
5
+ scope: consumer
6
+ requires: ['nx']
7
+ ---
8
+
9
+ ## Nx workspace layout
10
+
11
+ Apps are thin shells. All feature code lives in libs, grouped by kind, not by Nx type:
12
+
13
+ ```text
14
+ apps/<app>/src/app/ app.component.ts, app.config.ts, app.routes.ts only
15
+ libs/
16
+ domain/<app>/<feature>/ views, components, services, <feature>.routes.ts
17
+ domain/<app>/shared/ code shared by one app's features (optional)
18
+ queries/ query clients and creators, one folder per backend
19
+ types/ API models and shared types
20
+ uikit/ app-agnostic presentational components
21
+ theme/ surface and colour themes
22
+ env/ environment.ts, environment.production.ts
23
+ assets/ fonts, icons, images
24
+ ```
25
+
26
+ - `app.routes.ts` only lazy-loads domain libs:
27
+ `loadChildren: () => import('@acme/domain/admin/users').then((m) => m.USERS_ROUTES)`.
28
+ Each feature lib exports its own `<FEATURE>_ROUTES`.
29
+ - A small app may use one lib per app (`libs/domain/<app>`) with a folder per feature.
30
+ Never put features, queries, shared UI or theme in the app project.
31
+ - Do not add Nx `feature/ui/data-access/util` folders or `type:*` tags.
32
+ - Every lib has `src/index.ts` and explicit `build` (`@nx/angular:ng-packagr-lite`), `lint`
33
+ and `test` targets in `project.json`. Keep `plugins: []` in `nx.json`.
34
+ - Alias = path with slashes: `@acme/domain/admin/users`, `@acme/queries`, `@acme/types`,
35
+ `@acme/uikit`, `@acme/env`. Project name = path with dashes (`domain-admin-users`).
36
+ Component `prefix` = the repo abbreviation.
37
+ - Tag every project with one `scope:*`: `scope:<app>` for an app and its domain libs,
38
+ `scope:core` for env and types, plus `scope:queries`, `scope:uikit`, `scope:theme`.
39
+ `@nx/enforce-module-boundaries` with `enforceBuildableLibDependency: true` lets
40
+ `scope:<app>` depend on itself and the shared scopes, never on another app.
41
+ - A backend in the same workspace is an app in `apps/` with its libs in
42
+ `libs/domain/<name>-api`. Ban `@angular/*` in backend scopes and `@nestjs/*` in frontend
43
+ scopes with `bannedExternalImports`.
44
+ - Environment config lives only in `libs/env`, never hardcoded in a query client.
45
+
46
+ This is the layout for new workspaces and new code. Do not restructure an existing
47
+ workspace that deviates from it unless the user asks.
@@ -12,4 +12,4 @@ scope: both
12
12
  - **Bridge, don't copy.** Cross the boundary with `toSignal()` / `toObservable()`, never by
13
13
  `.subscribe()`-ing and assigning the value somewhere.
14
14
 
15
- Subscriptions, effects, and the traps in each direction: {%skill:rxjs-signals%}.
15
+ For subscriptions and effects, load the repository's focused reactive-state guidance.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: styling
3
- description: Component CSS is plain CSS in @layer components, and every colour comes from a theme token.
3
+ description: Component CSS is plain CSS in @layer components, and hardcoded colours are never primary values.
4
4
  kind: rule
5
- scope: both
5
+ scope: sdk
6
6
  requires: ['@ethlete/core']
7
7
  ---
8
8
 
@@ -26,10 +26,12 @@ utility can win.
26
26
  rule, so source order decides. Leave interaction states (`:hover`, `:focus-visible`,
27
27
  `:active`) bare so they escalate and win.
28
28
 
29
- **Never hardcode a colour.** Backgrounds, text, borders and interaction states all resolve
30
- from the surface and colour theming tokens (`--et-surface-*-solid`, `--et-theme-color-*`) —
31
- see {%skill:theming%}.
29
+ **Never use a hardcoded colour as the primary value.** Backgrounds, text, borders and
30
+ interaction states resolve from the surface and colour theming tokens
31
+ (`--et-surface-*-solid`, `--et-theme-color-*`). A static fallback inside
32
+ `var(--token, <fallback>)` is permitted, but not required.
32
33
 
33
34
  Theme **names** (`brand`, `danger`, `dark-elevated`, …) are registered by the application;
34
- the SDK ships none. Never hardcode a theme-name union in a type, a doc or an example —
35
- semantic colours resolve by theme `type` (e.g. `injectErrorTheme()`).
35
+ the SDK ships none. Never hardcode them as an SDK-defined union or reusable API contract.
36
+ If an app-specific example names one, label it as belonging to that app. Semantic colours
37
+ resolve by theme `type` (e.g. `injectSemanticColorTheme('error')`).
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: subagent-models
3
+ description: A subagent's model is an explicit choice on every call - haiku for lookups and small specified jobs, sonnet for scoped work, opus for hard work, fable for judgment.
4
+ kind: rule
5
+ scope: both
6
+ ---
7
+
8
+ ## Delegating: name the subagent's model
9
+
10
+ A subagent spawned without a `model` runs on the model leading the session, so an expensive
11
+ model ends up doing every small job it delegates. **Set `model` on every call**, matched to the
12
+ task:
13
+
14
+ | Model | The work it fits |
15
+ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `haiku` | Lookups and small, fully specified jobs: grep, find, read and summarize files or a CI log, run a command and report, a mechanical edit in named files. Haiku 5.5 does these well. |
17
+ | `sonnet` | Scoped work with a clear target: a fix in a named file, a spec, a docs page, a lint cleanup, research that needs reasoning. Sonnet 5.5 is close to opus here and costs less. |
18
+ | `opus` | Hard work: a bug with no known cause, a change across many files, a review of a diff, and any task sonnet did not finish. |
19
+ | `fable` | Judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most expensive of the four, so ask the user before picking it. |
20
+
21
+ Effort follows the prompt, not a parameter: scope the prompt to one question, name what "done"
22
+ means, and say "keep it brief" for a lookup. Two calls need no `model` of their own - a named
23
+ agent type carries the model and reasoning effort its own definition sets, and a fork always
24
+ inherits the parent's model.
25
+
26
+ A subagent does not have to be a Claude one. Delegate to a Codex agent where another model
27
+ family would answer better - a second opinion, a review of your own diff, a bug hunt your own
28
+ reading failed - and whenever the Claude usage limit is close, because a Codex run does not draw
29
+ on it. Read {%skill:codex-subagent%} first.